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