Merge branch 'fix/read-inbox' into 'develop'
[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.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
21
22 require Logger
23
24 action_fallback(:errors)
25
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])
29
30 def relay_active?(conn, _) do
31 if Pleroma.Config.get([:instance, :allow_relay]) do
32 conn
33 else
34 conn
35 |> render_error(:not_found, "not found")
36 |> halt()
37 end
38 end
39
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
43 conn
44 |> put_resp_content_type("application/activity+json")
45 |> json(UserView.render("user.json", %{user: user}))
46 else
47 nil -> {:error, :not_found}
48 end
49 end
50
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
55 conn
56 |> put_resp_content_type("application/activity+json")
57 |> json(ObjectView.render("object.json", %{object: object}))
58 else
59 {:public?, false} ->
60 {:error, :not_found}
61 end
62 end
63
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)
70
71 conn
72 |> put_resp_content_type("application/activity+json")
73 |> json(ObjectView.render("likes.json", ap_id, likes, page))
74 else
75 {:public?, false} ->
76 {:error, :not_found}
77 end
78 end
79
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
85 conn
86 |> put_resp_content_type("application/activity+json")
87 |> json(ObjectView.render("likes.json", ap_id, likes))
88 else
89 {:public?, false} ->
90 {:error, :not_found}
91 end
92 end
93
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
98 conn
99 |> put_resp_content_type("application/activity+json")
100 |> json(ObjectView.render("object.json", %{object: activity}))
101 else
102 {:public?, false} ->
103 {:error, :not_found}
104 end
105 end
106
107 # GET /relay/following
108 def following(%{assigns: %{relay: true}} = conn, _params) do
109 conn
110 |> put_resp_content_type("application/activity+json")
111 |> json(UserView.render("following.json", %{user: Relay.get_actor()}))
112 end
113
114 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
115 with %User{} = user <- User.get_cached_by_nickname(nickname),
116 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
117 {:show_follows, true} <-
118 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
119 {page, _} = Integer.parse(page)
120
121 conn
122 |> put_resp_content_type("application/activity+json")
123 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
124 else
125 {:show_follows, _} ->
126 conn
127 |> put_resp_content_type("application/activity+json")
128 |> send_resp(403, "")
129 end
130 end
131
132 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
133 with %User{} = user <- User.get_cached_by_nickname(nickname),
134 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
135 conn
136 |> put_resp_content_type("application/activity+json")
137 |> json(UserView.render("following.json", %{user: user, for: for_user}))
138 end
139 end
140
141 # GET /relay/followers
142 def followers(%{assigns: %{relay: true}} = conn, _params) do
143 conn
144 |> put_resp_content_type("application/activity+json")
145 |> json(UserView.render("followers.json", %{user: Relay.get_actor()}))
146 end
147
148 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
149 with %User{} = user <- User.get_cached_by_nickname(nickname),
150 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
151 {:show_followers, true} <-
152 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
153 {page, _} = Integer.parse(page)
154
155 conn
156 |> put_resp_content_type("application/activity+json")
157 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
158 else
159 {:show_followers, _} ->
160 conn
161 |> put_resp_content_type("application/activity+json")
162 |> send_resp(403, "")
163 end
164 end
165
166 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
167 with %User{} = user <- User.get_cached_by_nickname(nickname),
168 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
169 conn
170 |> put_resp_content_type("application/activity+json")
171 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
172 end
173 end
174
175 def outbox(conn, %{"nickname" => nickname} = params) do
176 with %User{} = user <- User.get_cached_by_nickname(nickname),
177 {:ok, user} <- User.ensure_keys_present(user) do
178 conn
179 |> put_resp_content_type("application/activity+json")
180 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
181 end
182 end
183
184 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
185 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
186 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
187 true <- Utils.recipient_in_message(recipient, actor, params),
188 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
189 Federator.incoming_ap_doc(params)
190 json(conn, "ok")
191 end
192 end
193
194 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
195 Federator.incoming_ap_doc(params)
196 json(conn, "ok")
197 end
198
199 # only accept relayed Creates
200 def inbox(conn, %{"type" => "Create"} = params) do
201 Logger.info(
202 "Signature missing or not from author, relayed Create message, fetching object from source"
203 )
204
205 Fetcher.fetch_object_from_id(params["object"]["id"])
206
207 json(conn, "ok")
208 end
209
210 def inbox(conn, params) do
211 headers = Enum.into(conn.req_headers, %{})
212
213 if String.contains?(headers["signature"], params["actor"]) do
214 Logger.info(
215 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
216 )
217
218 Logger.info(inspect(conn.req_headers))
219 end
220
221 json(conn, dgettext("errors", "error"))
222 end
223
224 defp represent_service_actor(%User{} = user, conn) do
225 with {:ok, user} <- User.ensure_keys_present(user) do
226 conn
227 |> put_resp_content_type("application/activity+json")
228 |> json(UserView.render("user.json", %{user: user}))
229 else
230 nil -> {:error, :not_found}
231 end
232 end
233
234 defp represent_service_actor(nil, _), do: {:error, :not_found}
235
236 def relay(conn, _params) do
237 Relay.get_actor()
238 |> represent_service_actor(conn)
239 end
240
241 def internal_fetch(conn, _params) do
242 InternalFetchActor.get_actor()
243 |> represent_service_actor(conn)
244 end
245
246 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
247 conn
248 |> put_resp_content_type("application/activity+json")
249 |> json(UserView.render("user.json", %{user: user}))
250 end
251
252 def whoami(_conn, _params), do: {:error, :not_found}
253
254 def read_inbox(
255 %{assigns: %{user: %{nickname: nickname} = user}} = conn,
256 %{"nickname" => nickname} = params
257 ) do
258 conn
259 |> put_resp_content_type("application/activity+json")
260 |> put_view(UserView)
261 |> render("inbox.json", user: user, max_id: params["max_id"])
262 end
263
264 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
265 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
266
267 conn
268 |> put_status(:forbidden)
269 |> json(err)
270 end
271
272 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
273 "nickname" => nickname
274 }) do
275 err =
276 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
277 nickname: nickname,
278 as_nickname: as_nickname
279 )
280
281 conn
282 |> put_status(:forbidden)
283 |> json(err)
284 end
285
286 def handle_user_activity(user, %{"type" => "Create"} = params) do
287 object =
288 params["object"]
289 |> Map.merge(Map.take(params, ["to", "cc"]))
290 |> Map.put("attributedTo", user.ap_id())
291 |> Transmogrifier.fix_object()
292
293 ActivityPub.create(%{
294 to: params["to"],
295 actor: user,
296 context: object["context"],
297 object: object,
298 additional: Map.take(params, ["cc"])
299 })
300 end
301
302 def handle_user_activity(user, %{"type" => "Delete"} = params) do
303 with %Object{} = object <- Object.normalize(params["object"]),
304 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
305 {:ok, delete} <- ActivityPub.delete(object) do
306 {:ok, delete}
307 else
308 _ -> {:error, dgettext("errors", "Can't delete object")}
309 end
310 end
311
312 def handle_user_activity(user, %{"type" => "Like"} = params) do
313 with %Object{} = object <- Object.normalize(params["object"]),
314 {:ok, activity, _object} <- ActivityPub.like(user, object) do
315 {:ok, activity}
316 else
317 _ -> {:error, dgettext("errors", "Can't like object")}
318 end
319 end
320
321 def handle_user_activity(_, _) do
322 {:error, dgettext("errors", "Unhandled activity type")}
323 end
324
325 def update_outbox(
326 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
327 %{"nickname" => nickname} = params
328 ) do
329 actor = user.ap_id()
330
331 params =
332 params
333 |> Map.drop(["id"])
334 |> Map.put("actor", actor)
335 |> Transmogrifier.fix_addressing()
336
337 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
338 conn
339 |> put_status(:created)
340 |> put_resp_header("location", activity.data["id"])
341 |> json(activity.data)
342 else
343 {:error, message} ->
344 conn
345 |> put_status(:bad_request)
346 |> json(message)
347 end
348 end
349
350 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
351 err =
352 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
353 nickname: nickname,
354 as_nickname: user.nickname
355 )
356
357 conn
358 |> put_status(:forbidden)
359 |> json(err)
360 end
361
362 def errors(conn, {:error, :not_found}) do
363 conn
364 |> put_status(:not_found)
365 |> json(dgettext("errors", "Not found"))
366 end
367
368 def errors(conn, _e) do
369 conn
370 |> put_status(:internal_server_error)
371 |> json(dgettext("errors", "error"))
372 end
373
374 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
375 with actor <- conn.params["actor"],
376 true <- is_binary(actor) do
377 Pleroma.Instances.set_reachable(actor)
378 end
379
380 conn
381 end
382
383 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
384 {:ok, new_user} = User.ensure_keys_present(user)
385
386 for_user =
387 if new_user != user and match?(%User{}, for_user) do
388 User.get_cached_by_nickname(for_user.nickname)
389 else
390 for_user
391 end
392
393 {new_user, for_user}
394 end
395 end