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.ObjectView
14 alias Pleroma.Web.ActivityPub.Relay
15 alias Pleroma.Web.ActivityPub.Transmogrifier
16 alias Pleroma.Web.ActivityPub.UserView
17 alias Pleroma.Web.ActivityPub.Utils
18 alias Pleroma.Web.ActivityPub.Visibility
19 alias Pleroma.Web.Federator
23 action_fallback(:errors)
25 plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
26 plug(:set_requester_reachable when action in [:inbox])
27 plug(:relay_active? when action in [:relay])
29 def relay_active?(conn, _) do
30 if Pleroma.Config.get([:instance, :allow_relay]) do
34 |> render_error(:not_found, "not found")
39 def user(conn, %{"nickname" => nickname}) do
40 with %User{} = user <- User.get_cached_by_nickname(nickname),
41 {:ok, user} <- User.ensure_keys_present(user) do
43 |> put_resp_header("content-type", "application/activity+json")
44 |> json(UserView.render("user.json", %{user: user}))
46 nil -> {:error, :not_found}
50 def object(conn, %{"uuid" => uuid}) do
51 with ap_id <- o_status_url(conn, :object, uuid),
52 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
53 {_, true} <- {:public?, Visibility.is_public?(object)} do
55 |> put_resp_header("content-type", "application/activity+json")
56 |> json(ObjectView.render("object.json", %{object: object}))
63 def object_likes(conn, %{"uuid" => uuid, "page" => page}) 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)},
67 likes <- Utils.get_object_likes(object) do
68 {page, _} = Integer.parse(page)
71 |> put_resp_header("content-type", "application/activity+json")
72 |> json(ObjectView.render("likes.json", ap_id, likes, page))
79 def object_likes(conn, %{"uuid" => uuid}) do
80 with ap_id <- o_status_url(conn, :object, uuid),
81 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
82 {_, true} <- {:public?, Visibility.is_public?(object)},
83 likes <- Utils.get_object_likes(object) do
85 |> put_resp_header("content-type", "application/activity+json")
86 |> json(ObjectView.render("likes.json", ap_id, likes))
93 def activity(conn, %{"uuid" => uuid}) do
94 with ap_id <- o_status_url(conn, :activity, uuid),
95 %Activity{} = activity <- Activity.normalize(ap_id),
96 {_, true} <- {:public?, Visibility.is_public?(activity)} do
98 |> put_resp_header("content-type", "application/activity+json")
99 |> json(ObjectView.render("object.json", %{object: activity}))
106 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
107 with %User{} = user <- User.get_cached_by_nickname(nickname),
108 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
109 {:show_follows, true} <-
110 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
111 {page, _} = Integer.parse(page)
114 |> put_resp_header("content-type", "application/activity+json")
115 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
117 {:show_follows, _} ->
119 |> put_resp_header("content-type", "application/activity+json")
120 |> send_resp(403, "")
124 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
125 with %User{} = user <- User.get_cached_by_nickname(nickname),
126 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
128 |> put_resp_header("content-type", "application/activity+json")
129 |> json(UserView.render("following.json", %{user: user, for: for_user}))
133 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
134 with %User{} = user <- User.get_cached_by_nickname(nickname),
135 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
136 {:show_followers, true} <-
137 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
138 {page, _} = Integer.parse(page)
141 |> put_resp_header("content-type", "application/activity+json")
142 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
144 {:show_followers, _} ->
146 |> put_resp_header("content-type", "application/activity+json")
147 |> send_resp(403, "")
151 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
152 with %User{} = user <- User.get_cached_by_nickname(nickname),
153 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
155 |> put_resp_header("content-type", "application/activity+json")
156 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
160 def outbox(conn, %{"nickname" => nickname} = params) do
161 with %User{} = user <- User.get_cached_by_nickname(nickname),
162 {:ok, user} <- User.ensure_keys_present(user) do
164 |> put_resp_header("content-type", "application/activity+json")
165 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
169 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
170 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
171 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
172 true <- Utils.recipient_in_message(recipient, actor, params),
173 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
174 Federator.incoming_ap_doc(params)
179 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
180 Federator.incoming_ap_doc(params)
184 # only accept relayed Creates
185 def inbox(conn, %{"type" => "Create"} = params) do
187 "Signature missing or not from author, relayed Create message, fetching object from source"
190 Fetcher.fetch_object_from_id(params["object"]["id"])
195 def inbox(conn, params) do
196 headers = Enum.into(conn.req_headers, %{})
198 if String.contains?(headers["signature"], params["actor"]) do
200 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
203 Logger.info(inspect(conn.req_headers))
206 json(conn, dgettext("errors", "error"))
209 def relay(conn, _params) do
210 with %User{} = user <- Relay.get_actor(),
211 {:ok, user} <- User.ensure_keys_present(user) do
213 |> put_resp_header("content-type", "application/activity+json")
214 |> json(UserView.render("user.json", %{user: user}))
216 nil -> {:error, :not_found}
220 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
222 |> put_resp_header("content-type", "application/activity+json")
223 |> json(UserView.render("user.json", %{user: user}))
226 def whoami(_conn, _params), do: {:error, :not_found}
228 def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
229 if nickname == user.nickname do
231 |> put_resp_header("content-type", "application/activity+json")
232 |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
235 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
237 as_nickname: user.nickname
241 |> put_status(:forbidden)
246 def handle_user_activity(user, %{"type" => "Create"} = params) do
249 |> Map.merge(Map.take(params, ["to", "cc"]))
250 |> Map.put("attributedTo", user.ap_id())
251 |> Transmogrifier.fix_object()
253 ActivityPub.create(%{
256 context: object["context"],
258 additional: Map.take(params, ["cc"])
262 def handle_user_activity(user, %{"type" => "Delete"} = params) do
263 with %Object{} = object <- Object.normalize(params["object"]),
264 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
265 {:ok, delete} <- ActivityPub.delete(object) do
268 _ -> {:error, dgettext("errors", "Can't delete object")}
272 def handle_user_activity(user, %{"type" => "Like"} = params) do
273 with %Object{} = object <- Object.normalize(params["object"]),
274 {:ok, activity, _object} <- ActivityPub.like(user, object) do
277 _ -> {:error, dgettext("errors", "Can't like object")}
281 def handle_user_activity(_, _) do
282 {:error, dgettext("errors", "Unhandled activity type")}
286 %{assigns: %{user: user}} = conn,
287 %{"nickname" => nickname} = params
289 if nickname == user.nickname do
295 |> Map.put("actor", actor)
296 |> Transmogrifier.fix_addressing()
298 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
300 |> put_status(:created)
301 |> put_resp_header("location", activity.data["id"])
302 |> json(activity.data)
306 |> put_status(:bad_request)
311 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
313 as_nickname: user.nickname
317 |> put_status(:forbidden)
322 def errors(conn, {:error, :not_found}) do
324 |> put_status(:not_found)
325 |> json(dgettext("errors", "Not found"))
328 def errors(conn, _e) do
330 |> put_status(:internal_server_error)
331 |> json(dgettext("errors", "error"))
334 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
335 with actor <- conn.params["actor"],
336 true <- is_binary(actor) do
337 Pleroma.Instances.set_reachable(actor)
343 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
344 {:ok, new_user} = User.ensure_keys_present(user)
347 if new_user != user and match?(%User{}, for_user) do
348 User.get_cached_by_nickname(for_user.nickname)