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