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