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