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
11 alias Pleroma.Web.ActivityPub.ActivityPub
12 alias Pleroma.Web.ActivityPub.ObjectView
13 alias Pleroma.Web.ActivityPub.Relay
14 alias Pleroma.Web.ActivityPub.Transmogrifier
15 alias Pleroma.Web.ActivityPub.UserView
16 alias Pleroma.Web.ActivityPub.Utils
17 alias Pleroma.Web.ActivityPub.Visibility
18 alias Pleroma.Web.Federator
22 action_fallback(:errors)
24 plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
25 plug(:set_requester_reachable when action in [:inbox])
26 plug(:relay_active? when action in [:relay])
28 def relay_active?(conn, _) do
29 if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do
34 |> json(%{error: "not found"})
39 def user(conn, %{"nickname" => nickname}) do
40 with %User{} = user <- User.get_cached_by_nickname(nickname),
41 {:ok, user} <- Pleroma.Web.WebFinger.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} <- Pleroma.Web.WebFinger.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} <- Pleroma.Web.WebFinger.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} <- Pleroma.Web.WebFinger.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} <- Pleroma.Web.WebFinger.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} <- Pleroma.Web.WebFinger.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{} = user <- User.get_cached_by_nickname(nickname),
157 true <- Utils.recipient_in_message(user.ap_id, params),
158 params <- Utils.maybe_splice_recipient(user.ap_id, params) do
159 Federator.incoming_ap_doc(params)
164 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
165 Federator.incoming_ap_doc(params)
169 # only accept relayed Creates
170 def inbox(conn, %{"type" => "Create"} = params) do
172 "Signature missing or not from author, relayed Create message, fetching object from source"
175 ActivityPub.fetch_object_from_id(params["object"]["id"])
180 def inbox(conn, params) do
181 headers = Enum.into(conn.req_headers, %{})
183 if String.contains?(headers["signature"], params["actor"]) do
185 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
188 Logger.info(inspect(conn.req_headers))
194 def relay(conn, _params) do
195 with %User{} = user <- Relay.get_actor(),
196 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
198 |> put_resp_header("content-type", "application/activity+json")
199 |> json(UserView.render("user.json", %{user: user}))
201 nil -> {:error, :not_found}
205 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
207 |> put_resp_header("content-type", "application/activity+json")
208 |> json(UserView.render("user.json", %{user: user}))
211 def whoami(_conn, _params), do: {:error, :not_found}
213 def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
214 if nickname == user.nickname do
216 |> put_resp_header("content-type", "application/activity+json")
217 |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
220 |> put_status(:forbidden)
221 |> json("can't read inbox of #{nickname} as #{user.nickname}")
225 def handle_user_activity(user, %{"type" => "Create"} = params) do
228 |> Map.merge(Map.take(params, ["to", "cc"]))
229 |> Map.put("attributedTo", user.ap_id())
230 |> Transmogrifier.fix_object()
232 ActivityPub.create(%{
235 context: object["context"],
237 additional: Map.take(params, ["cc"])
241 def handle_user_activity(user, %{"type" => "Delete"} = params) do
242 with %Object{} = object <- Object.normalize(params["object"]),
243 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
244 {:ok, delete} <- ActivityPub.delete(object) do
247 _ -> {:error, "Can't delete object"}
251 def handle_user_activity(user, %{"type" => "Like"} = params) do
252 with %Object{} = object <- Object.normalize(params["object"]),
253 {:ok, activity, _object} <- ActivityPub.like(user, object) do
256 _ -> {:error, "Can't like object"}
260 def handle_user_activity(_, _) do
261 {:error, "Unhandled activity type"}
265 %{assigns: %{user: user}} = conn,
266 %{"nickname" => nickname} = params
268 if nickname == user.nickname do
274 |> Map.put("actor", actor)
275 |> Transmogrifier.fix_addressing()
277 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
279 |> put_status(:created)
280 |> put_resp_header("location", activity.data["id"])
281 |> json(activity.data)
285 |> put_status(:bad_request)
290 |> put_status(:forbidden)
291 |> json("can't update outbox of #{nickname} as #{user.nickname}")
295 def errors(conn, {:error, :not_found}) do
301 def errors(conn, _e) do
307 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
308 with actor <- conn.params["actor"],
309 true <- is_binary(actor) do
310 Pleroma.Instances.set_reachable(actor)