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_header("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_header("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_header("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_header("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_header("content-type", "application/activity+json")
100 |> json(ObjectView.render("object.json", %{object: activity}))
107 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
108 with %User{} = user <- User.get_cached_by_nickname(nickname),
109 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
110 {:show_follows, true} <-
111 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
112 {page, _} = Integer.parse(page)
115 |> put_resp_header("content-type", "application/activity+json")
116 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
118 {:show_follows, _} ->
120 |> put_resp_header("content-type", "application/activity+json")
121 |> send_resp(403, "")
125 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
126 with %User{} = user <- User.get_cached_by_nickname(nickname),
127 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
129 |> put_resp_header("content-type", "application/activity+json")
130 |> json(UserView.render("following.json", %{user: user, for: for_user}))
134 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
135 with %User{} = user <- User.get_cached_by_nickname(nickname),
136 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
137 {:show_followers, true} <-
138 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
139 {page, _} = Integer.parse(page)
142 |> put_resp_header("content-type", "application/activity+json")
143 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
145 {:show_followers, _} ->
147 |> put_resp_header("content-type", "application/activity+json")
148 |> send_resp(403, "")
152 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
153 with %User{} = user <- User.get_cached_by_nickname(nickname),
154 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
156 |> put_resp_header("content-type", "application/activity+json")
157 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
161 def outbox(conn, %{"nickname" => nickname} = params) do
162 with %User{} = user <- User.get_cached_by_nickname(nickname),
163 {:ok, user} <- User.ensure_keys_present(user) do
165 |> put_resp_header("content-type", "application/activity+json")
166 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
170 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
171 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
172 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
173 true <- Utils.recipient_in_message(recipient, actor, params),
174 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
175 Federator.incoming_ap_doc(params)
180 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
181 Federator.incoming_ap_doc(params)
185 # only accept relayed Creates
186 def inbox(conn, %{"type" => "Create"} = params) do
188 "Signature missing or not from author, relayed Create message, fetching object from source"
191 Fetcher.fetch_object_from_id(params["object"]["id"])
196 def inbox(conn, params) do
197 headers = Enum.into(conn.req_headers, %{})
199 if String.contains?(headers["signature"], params["actor"]) do
201 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
204 Logger.info(inspect(conn.req_headers))
207 json(conn, dgettext("errors", "error"))
210 defp represent_service_actor(%User{} = user, conn) do
211 with {: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 defp represent_service_actor(nil, _), do: {:error, :not_found}
222 def relay(conn, _params) do
224 |> represent_service_actor(conn)
227 def internal_fetch(conn, _params) do
228 InternalFetchActor.get_actor()
229 |> represent_service_actor(conn)
232 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
234 |> put_resp_header("content-type", "application/activity+json")
235 |> json(UserView.render("user.json", %{user: user}))
238 def whoami(_conn, _params), do: {:error, :not_found}
240 def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
241 if nickname == user.nickname do
243 |> put_resp_header("content-type", "application/activity+json")
244 |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
247 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
249 as_nickname: user.nickname
253 |> put_status(:forbidden)
258 def handle_user_activity(user, %{"type" => "Create"} = params) do
261 |> Map.merge(Map.take(params, ["to", "cc"]))
262 |> Map.put("attributedTo", user.ap_id())
263 |> Transmogrifier.fix_object()
265 ActivityPub.create(%{
268 context: object["context"],
270 additional: Map.take(params, ["cc"])
274 def handle_user_activity(user, %{"type" => "Delete"} = params) do
275 with %Object{} = object <- Object.normalize(params["object"]),
276 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
277 {:ok, delete} <- ActivityPub.delete(object) do
280 _ -> {:error, dgettext("errors", "Can't delete object")}
284 def handle_user_activity(user, %{"type" => "Like"} = params) do
285 with %Object{} = object <- Object.normalize(params["object"]),
286 {:ok, activity, _object} <- ActivityPub.like(user, object) do
289 _ -> {:error, dgettext("errors", "Can't like object")}
293 def handle_user_activity(_, _) do
294 {:error, dgettext("errors", "Unhandled activity type")}
298 %{assigns: %{user: user}} = conn,
299 %{"nickname" => nickname} = params
301 if nickname == user.nickname do
307 |> Map.put("actor", actor)
308 |> Transmogrifier.fix_addressing()
310 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
312 |> put_status(:created)
313 |> put_resp_header("location", activity.data["id"])
314 |> json(activity.data)
318 |> put_status(:bad_request)
323 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
325 as_nickname: user.nickname
329 |> put_status(:forbidden)
334 def errors(conn, {:error, :not_found}) do
336 |> put_status(:not_found)
337 |> json(dgettext("errors", "Not found"))
340 def errors(conn, _e) do
342 |> put_status(:internal_server_error)
343 |> json(dgettext("errors", "error"))
346 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
347 with actor <- conn.params["actor"],
348 true <- is_binary(actor) do
349 Pleroma.Instances.set_reachable(actor)
355 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
356 {:ok, new_user} = User.ensure_keys_present(user)
359 if new_user != user and match?(%User{}, for_user) do
360 User.get_cached_by_nickname(for_user.nickname)