activitypub: controller: rework the way the relay actor is presented so the code...
[akkoma] / lib / pleroma / web / activity_pub / activity_pub_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.ActivityPubController do
6 use Pleroma.Web, :controller
7
8 alias Pleroma.Activity
9 alias Pleroma.Object
10 alias Pleroma.Object.Fetcher
11 alias Pleroma.User
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
20
21 require Logger
22
23 action_fallback(:errors)
24
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])
28
29 def relay_active?(conn, _) do
30 if Pleroma.Config.get([:instance, :allow_relay]) do
31 conn
32 else
33 conn
34 |> render_error(:not_found, "not found")
35 |> halt()
36 end
37 end
38
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
42 conn
43 |> put_resp_header("content-type", "application/activity+json")
44 |> json(UserView.render("user.json", %{user: user}))
45 else
46 nil -> {:error, :not_found}
47 end
48 end
49
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
54 conn
55 |> put_resp_header("content-type", "application/activity+json")
56 |> json(ObjectView.render("object.json", %{object: object}))
57 else
58 {:public?, false} ->
59 {:error, :not_found}
60 end
61 end
62
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)
69
70 conn
71 |> put_resp_header("content-type", "application/activity+json")
72 |> json(ObjectView.render("likes.json", ap_id, likes, page))
73 else
74 {:public?, false} ->
75 {:error, :not_found}
76 end
77 end
78
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
84 conn
85 |> put_resp_header("content-type", "application/activity+json")
86 |> json(ObjectView.render("likes.json", ap_id, likes))
87 else
88 {:public?, false} ->
89 {:error, :not_found}
90 end
91 end
92
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
97 conn
98 |> put_resp_header("content-type", "application/activity+json")
99 |> json(ObjectView.render("object.json", %{object: activity}))
100 else
101 {:public?, false} ->
102 {:error, :not_found}
103 end
104 end
105
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)
112
113 conn
114 |> put_resp_header("content-type", "application/activity+json")
115 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
116 else
117 {:show_follows, _} ->
118 conn
119 |> put_resp_header("content-type", "application/activity+json")
120 |> send_resp(403, "")
121 end
122 end
123
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
127 conn
128 |> put_resp_header("content-type", "application/activity+json")
129 |> json(UserView.render("following.json", %{user: user, for: for_user}))
130 end
131 end
132
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)
139
140 conn
141 |> put_resp_header("content-type", "application/activity+json")
142 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
143 else
144 {:show_followers, _} ->
145 conn
146 |> put_resp_header("content-type", "application/activity+json")
147 |> send_resp(403, "")
148 end
149 end
150
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
154 conn
155 |> put_resp_header("content-type", "application/activity+json")
156 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
157 end
158 end
159
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
163 conn
164 |> put_resp_header("content-type", "application/activity+json")
165 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
166 end
167 end
168
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)
175 json(conn, "ok")
176 end
177 end
178
179 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
180 Federator.incoming_ap_doc(params)
181 json(conn, "ok")
182 end
183
184 # only accept relayed Creates
185 def inbox(conn, %{"type" => "Create"} = params) do
186 Logger.info(
187 "Signature missing or not from author, relayed Create message, fetching object from source"
188 )
189
190 Fetcher.fetch_object_from_id(params["object"]["id"])
191
192 json(conn, "ok")
193 end
194
195 def inbox(conn, params) do
196 headers = Enum.into(conn.req_headers, %{})
197
198 if String.contains?(headers["signature"], params["actor"]) do
199 Logger.info(
200 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
201 )
202
203 Logger.info(inspect(conn.req_headers))
204 end
205
206 json(conn, dgettext("errors", "error"))
207 end
208
209 defp represent_service_actor(%User{} = user, conn) do
210 with {:ok, user} <- User.ensure_keys_present(user) do
211 conn
212 |> put_resp_header("content-type", "application/activity+json")
213 |> json(UserView.render("user.json", %{user: user}))
214 else
215 nil -> {:error, :not_found}
216 end
217 end
218
219 defp represent_service_actor(nil, _), do: {:error, :not_found}
220
221 def relay(conn, _params) do
222 Relay.get_actor()
223 |> represent_service_actor(conn)
224 end
225
226 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
227 conn
228 |> put_resp_header("content-type", "application/activity+json")
229 |> json(UserView.render("user.json", %{user: user}))
230 end
231
232 def whoami(_conn, _params), do: {:error, :not_found}
233
234 def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
235 if nickname == user.nickname do
236 conn
237 |> put_resp_header("content-type", "application/activity+json")
238 |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
239 else
240 err =
241 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
242 nickname: nickname,
243 as_nickname: user.nickname
244 )
245
246 conn
247 |> put_status(:forbidden)
248 |> json(err)
249 end
250 end
251
252 def handle_user_activity(user, %{"type" => "Create"} = params) do
253 object =
254 params["object"]
255 |> Map.merge(Map.take(params, ["to", "cc"]))
256 |> Map.put("attributedTo", user.ap_id())
257 |> Transmogrifier.fix_object()
258
259 ActivityPub.create(%{
260 to: params["to"],
261 actor: user,
262 context: object["context"],
263 object: object,
264 additional: Map.take(params, ["cc"])
265 })
266 end
267
268 def handle_user_activity(user, %{"type" => "Delete"} = params) do
269 with %Object{} = object <- Object.normalize(params["object"]),
270 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
271 {:ok, delete} <- ActivityPub.delete(object) do
272 {:ok, delete}
273 else
274 _ -> {:error, dgettext("errors", "Can't delete object")}
275 end
276 end
277
278 def handle_user_activity(user, %{"type" => "Like"} = params) do
279 with %Object{} = object <- Object.normalize(params["object"]),
280 {:ok, activity, _object} <- ActivityPub.like(user, object) do
281 {:ok, activity}
282 else
283 _ -> {:error, dgettext("errors", "Can't like object")}
284 end
285 end
286
287 def handle_user_activity(_, _) do
288 {:error, dgettext("errors", "Unhandled activity type")}
289 end
290
291 def update_outbox(
292 %{assigns: %{user: user}} = conn,
293 %{"nickname" => nickname} = params
294 ) do
295 if nickname == user.nickname do
296 actor = user.ap_id()
297
298 params =
299 params
300 |> Map.drop(["id"])
301 |> Map.put("actor", actor)
302 |> Transmogrifier.fix_addressing()
303
304 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
305 conn
306 |> put_status(:created)
307 |> put_resp_header("location", activity.data["id"])
308 |> json(activity.data)
309 else
310 {:error, message} ->
311 conn
312 |> put_status(:bad_request)
313 |> json(message)
314 end
315 else
316 err =
317 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
318 nickname: nickname,
319 as_nickname: user.nickname
320 )
321
322 conn
323 |> put_status(:forbidden)
324 |> json(err)
325 end
326 end
327
328 def errors(conn, {:error, :not_found}) do
329 conn
330 |> put_status(:not_found)
331 |> json(dgettext("errors", "Not found"))
332 end
333
334 def errors(conn, _e) do
335 conn
336 |> put_status(:internal_server_error)
337 |> json(dgettext("errors", "error"))
338 end
339
340 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
341 with actor <- conn.params["actor"],
342 true <- is_binary(actor) do
343 Pleroma.Instances.set_reachable(actor)
344 end
345
346 conn
347 end
348
349 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
350 {:ok, new_user} = User.ensure_keys_present(user)
351
352 for_user =
353 if new_user != user and match?(%User{}, for_user) do
354 User.get_cached_by_nickname(for_user.nickname)
355 else
356 for_user
357 end
358
359 {new_user, for_user}
360 end
361 end