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(conn, %{"nickname" => nickname, "page" => page}) do
107 with %User{} = user <- User.get_cached_by_nickname(nickname),
108 {:ok, user} <- User.ensure_keys_present(user) do
109 {page, _} = Integer.parse(page)
112 |> put_resp_header("content-type", "application/activity+json")
113 |> json(UserView.render("following.json", %{user: user, page: page}))
117 def following(conn, %{"nickname" => nickname}) do
118 with %User{} = user <- User.get_cached_by_nickname(nickname),
119 {:ok, user} <- User.ensure_keys_present(user) do
121 |> put_resp_header("content-type", "application/activity+json")
122 |> json(UserView.render("following.json", %{user: user}))
126 def followers(conn, %{"nickname" => nickname, "page" => page}) do
127 with %User{} = user <- User.get_cached_by_nickname(nickname),
128 {:ok, user} <- User.ensure_keys_present(user) do
129 {page, _} = Integer.parse(page)
132 |> put_resp_header("content-type", "application/activity+json")
133 |> json(UserView.render("followers.json", %{user: user, page: page}))
137 def followers(conn, %{"nickname" => nickname}) do
138 with %User{} = user <- User.get_cached_by_nickname(nickname),
139 {:ok, user} <- User.ensure_keys_present(user) do
141 |> put_resp_header("content-type", "application/activity+json")
142 |> json(UserView.render("followers.json", %{user: user}))
146 def outbox(conn, %{"nickname" => nickname} = params) do
147 with %User{} = user <- User.get_cached_by_nickname(nickname),
148 {:ok, user} <- User.ensure_keys_present(user) do
150 |> put_resp_header("content-type", "application/activity+json")
151 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
155 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
156 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
157 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
158 true <- Utils.recipient_in_message(recipient, actor, params),
159 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
160 Federator.incoming_ap_doc(params)
165 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
166 Federator.incoming_ap_doc(params)
170 # only accept relayed Creates
171 def inbox(conn, %{"type" => "Create"} = params) do
173 "Signature missing or not from author, relayed Create message, fetching object from source"
176 Fetcher.fetch_object_from_id(params["object"]["id"])
181 def inbox(conn, params) do
182 headers = Enum.into(conn.req_headers, %{})
184 if String.contains?(headers["signature"], params["actor"]) do
186 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
189 Logger.info(inspect(conn.req_headers))
192 json(conn, dgettext("errors", "error"))
195 def relay(conn, _params) do
196 with %User{} = user <- Relay.get_actor(),
197 {:ok, user} <- User.ensure_keys_present(user) do
199 |> put_resp_header("content-type", "application/activity+json")
200 |> json(UserView.render("user.json", %{user: user}))
202 nil -> {:error, :not_found}
206 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
208 |> put_resp_header("content-type", "application/activity+json")
209 |> json(UserView.render("user.json", %{user: user}))
212 def whoami(_conn, _params), do: {:error, :not_found}
214 def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
215 if nickname == user.nickname do
217 |> put_resp_header("content-type", "application/activity+json")
218 |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
221 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
223 as_nickname: user.nickname
227 |> put_status(:forbidden)
232 def handle_user_activity(user, %{"type" => "Create"} = params) do
235 |> Map.merge(Map.take(params, ["to", "cc"]))
236 |> Map.put("attributedTo", user.ap_id())
237 |> Transmogrifier.fix_object()
239 ActivityPub.create(%{
242 context: object["context"],
244 additional: Map.take(params, ["cc"])
248 def handle_user_activity(user, %{"type" => "Delete"} = params) do
249 with %Object{} = object <- Object.normalize(params["object"]),
250 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
251 {:ok, delete} <- ActivityPub.delete(object) do
254 _ -> {:error, dgettext("errors", "Can't delete object")}
258 def handle_user_activity(user, %{"type" => "Like"} = params) do
259 with %Object{} = object <- Object.normalize(params["object"]),
260 {:ok, activity, _object} <- ActivityPub.like(user, object) do
263 _ -> {:error, dgettext("errors", "Can't like object")}
267 def handle_user_activity(_, _) do
268 {:error, dgettext("errors", "Unhandled activity type")}
272 %{assigns: %{user: user}} = conn,
273 %{"nickname" => nickname} = params
275 if nickname == user.nickname do
281 |> Map.put("actor", actor)
282 |> Transmogrifier.fix_addressing()
284 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
286 |> put_status(:created)
287 |> put_resp_header("location", activity.data["id"])
288 |> json(activity.data)
292 |> put_status(:bad_request)
297 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
299 as_nickname: user.nickname
303 |> put_status(:forbidden)
308 def errors(conn, {:error, :not_found}) do
310 |> put_status(:not_found)
311 |> json(dgettext("errors", "Not found"))
314 def errors(conn, _e) do
316 |> put_status(:internal_server_error)
317 |> json(dgettext("errors", "error"))
320 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
321 with actor <- conn.params["actor"],
322 true <- is_binary(actor) do
323 Pleroma.Instances.set_reachable(actor)