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 Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do
35 |> json(%{error: "not found"})
40 def user(conn, %{"nickname" => nickname}) do
41 with %User{} = user <- User.get_cached_by_nickname(nickname),
42 {:ok, user} <- Pleroma.Web.WebFinger.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(conn, %{"nickname" => nickname, "page" => page}) do
108 with %User{} = user <- User.get_cached_by_nickname(nickname),
109 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
110 {page, _} = Integer.parse(page)
113 |> put_resp_header("content-type", "application/activity+json")
114 |> json(UserView.render("following.json", %{user: user, page: page}))
118 def following(conn, %{"nickname" => nickname}) do
119 with %User{} = user <- User.get_cached_by_nickname(nickname),
120 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
122 |> put_resp_header("content-type", "application/activity+json")
123 |> json(UserView.render("following.json", %{user: user}))
127 def followers(conn, %{"nickname" => nickname, "page" => page}) do
128 with %User{} = user <- User.get_cached_by_nickname(nickname),
129 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
130 {page, _} = Integer.parse(page)
133 |> put_resp_header("content-type", "application/activity+json")
134 |> json(UserView.render("followers.json", %{user: user, page: page}))
138 def followers(conn, %{"nickname" => nickname}) do
139 with %User{} = user <- User.get_cached_by_nickname(nickname),
140 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
142 |> put_resp_header("content-type", "application/activity+json")
143 |> json(UserView.render("followers.json", %{user: user}))
147 def outbox(conn, %{"nickname" => nickname} = params) do
148 with %User{} = user <- User.get_cached_by_nickname(nickname),
149 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
151 |> put_resp_header("content-type", "application/activity+json")
152 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
156 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
157 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
158 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
159 true <- Utils.recipient_in_message(recipient, actor, params),
160 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
161 Federator.incoming_ap_doc(params)
166 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
167 Federator.incoming_ap_doc(params)
171 # only accept relayed Creates
172 def inbox(conn, %{"type" => "Create"} = params) do
174 "Signature missing or not from author, relayed Create message, fetching object from source"
177 Fetcher.fetch_object_from_id(params["object"]["id"])
182 def inbox(conn, params) do
183 headers = Enum.into(conn.req_headers, %{})
185 if String.contains?(headers["signature"], params["actor"]) do
187 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
190 Logger.info(inspect(conn.req_headers))
196 def relay(conn, _params) do
197 with %User{} = user <- Relay.get_actor(),
198 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
200 |> put_resp_header("content-type", "application/activity+json")
201 |> json(UserView.render("user.json", %{user: user}))
203 nil -> {:error, :not_found}
207 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
209 |> put_resp_header("content-type", "application/activity+json")
210 |> json(UserView.render("user.json", %{user: user}))
213 def whoami(_conn, _params), do: {:error, :not_found}
215 def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
216 if nickname == user.nickname do
218 |> put_resp_header("content-type", "application/activity+json")
219 |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
222 |> put_status(:forbidden)
223 |> json("can't read inbox of #{nickname} as #{user.nickname}")
227 def handle_user_activity(user, %{"type" => "Create"} = params) do
230 |> Map.merge(Map.take(params, ["to", "cc"]))
231 |> Map.put("attributedTo", user.ap_id())
232 |> Transmogrifier.fix_object()
234 ActivityPub.create(%{
237 context: object["context"],
239 additional: Map.take(params, ["cc"])
243 def handle_user_activity(user, %{"type" => "Delete"} = params) do
244 with %Object{} = object <- Object.normalize(params["object"]),
245 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
246 {:ok, delete} <- ActivityPub.delete(object) do
249 _ -> {:error, "Can't delete object"}
253 def handle_user_activity(user, %{"type" => "Like"} = params) do
254 with %Object{} = object <- Object.normalize(params["object"]),
255 {:ok, activity, _object} <- ActivityPub.like(user, object) do
258 _ -> {:error, "Can't like object"}
262 def handle_user_activity(_, _) do
263 {:error, "Unhandled activity type"}
267 %{assigns: %{user: user}} = conn,
268 %{"nickname" => nickname} = params
270 if nickname == user.nickname do
276 |> Map.put("actor", actor)
277 |> Transmogrifier.fix_addressing()
279 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
281 |> put_status(:created)
282 |> put_resp_header("location", activity.data["id"])
283 |> json(activity.data)
287 |> put_status(:bad_request)
292 |> put_status(:forbidden)
293 |> json("can't update outbox of #{nickname} as #{user.nickname}")
297 def errors(conn, {:error, :not_found}) do
303 def errors(conn, _e) do
309 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
310 with actor <- conn.params["actor"],
311 true <- is_binary(actor) do
312 Pleroma.Instances.set_reachable(actor)