Merge branch 'hotfix/delete-activities' 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.User
11 alias Pleroma.Web.ActivityPub.ActivityPub
12 alias Pleroma.Web.ActivityPub.ObjectView
13 alias Pleroma.Web.ActivityPub.Relay
14 alias Pleroma.Web.ActivityPub.Transmogrifier
15 alias Pleroma.Web.ActivityPub.UserView
16 alias Pleroma.Web.ActivityPub.Utils
17 alias Pleroma.Web.ActivityPub.Visibility
18 alias Pleroma.Web.Federator
19
20 require Logger
21
22 action_fallback(:errors)
23
24 plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
25 plug(:set_requester_reachable when action in [:inbox])
26 plug(:relay_active? when action in [:relay])
27
28 def relay_active?(conn, _) do
29 if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do
30 conn
31 else
32 conn
33 |> put_status(404)
34 |> json(%{error: "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} <- Pleroma.Web.WebFinger.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} <- Pleroma.Web.WebFinger.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} <- Pleroma.Web.WebFinger.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} <- Pleroma.Web.WebFinger.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} <- Pleroma.Web.WebFinger.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} <- Pleroma.Web.WebFinger.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{} = user <- User.get_cached_by_nickname(nickname),
157 true <- Utils.recipient_in_message(user.ap_id, params),
158 params <- Utils.maybe_splice_recipient(user.ap_id, params) do
159 Federator.incoming_ap_doc(params)
160 json(conn, "ok")
161 end
162 end
163
164 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
165 Federator.incoming_ap_doc(params)
166 json(conn, "ok")
167 end
168
169 # only accept relayed Creates
170 def inbox(conn, %{"type" => "Create"} = params) do
171 Logger.info(
172 "Signature missing or not from author, relayed Create message, fetching object from source"
173 )
174
175 ActivityPub.fetch_object_from_id(params["object"]["id"])
176
177 json(conn, "ok")
178 end
179
180 def inbox(conn, params) do
181 headers = Enum.into(conn.req_headers, %{})
182
183 if String.contains?(headers["signature"], params["actor"]) do
184 Logger.info(
185 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
186 )
187
188 Logger.info(inspect(conn.req_headers))
189 end
190
191 json(conn, "error")
192 end
193
194 def relay(conn, _params) do
195 with %User{} = user <- Relay.get_actor(),
196 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
197 conn
198 |> put_resp_header("content-type", "application/activity+json")
199 |> json(UserView.render("user.json", %{user: user}))
200 else
201 nil -> {:error, :not_found}
202 end
203 end
204
205 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
206 conn
207 |> put_resp_header("content-type", "application/activity+json")
208 |> json(UserView.render("user.json", %{user: user}))
209 end
210
211 def whoami(_conn, _params), do: {:error, :not_found}
212
213 def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
214 if nickname == user.nickname do
215 conn
216 |> put_resp_header("content-type", "application/activity+json")
217 |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
218 else
219 conn
220 |> put_status(:forbidden)
221 |> json("can't read inbox of #{nickname} as #{user.nickname}")
222 end
223 end
224
225 def handle_user_activity(user, %{"type" => "Create"} = params) do
226 object =
227 params["object"]
228 |> Map.merge(Map.take(params, ["to", "cc"]))
229 |> Map.put("attributedTo", user.ap_id())
230 |> Transmogrifier.fix_object()
231
232 ActivityPub.create(%{
233 to: params["to"],
234 actor: user,
235 context: object["context"],
236 object: object,
237 additional: Map.take(params, ["cc"])
238 })
239 end
240
241 def handle_user_activity(user, %{"type" => "Delete"} = params) do
242 with %Object{} = object <- Object.normalize(params["object"]),
243 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
244 {:ok, delete} <- ActivityPub.delete(object) do
245 {:ok, delete}
246 else
247 _ -> {:error, "Can't delete object"}
248 end
249 end
250
251 def handle_user_activity(user, %{"type" => "Like"} = params) do
252 with %Object{} = object <- Object.normalize(params["object"]),
253 {:ok, activity, _object} <- ActivityPub.like(user, object) do
254 {:ok, activity}
255 else
256 _ -> {:error, "Can't like object"}
257 end
258 end
259
260 def handle_user_activity(_, _) do
261 {:error, "Unhandled activity type"}
262 end
263
264 def update_outbox(
265 %{assigns: %{user: user}} = conn,
266 %{"nickname" => nickname} = params
267 ) do
268 if nickname == user.nickname do
269 actor = user.ap_id()
270
271 params =
272 params
273 |> Map.drop(["id"])
274 |> Map.put("actor", actor)
275 |> Transmogrifier.fix_addressing()
276
277 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
278 conn
279 |> put_status(:created)
280 |> put_resp_header("location", activity.data["id"])
281 |> json(activity.data)
282 else
283 {:error, message} ->
284 conn
285 |> put_status(:bad_request)
286 |> json(message)
287 end
288 else
289 conn
290 |> put_status(:forbidden)
291 |> json("can't update outbox of #{nickname} as #{user.nickname}")
292 end
293 end
294
295 def errors(conn, {:error, :not_found}) do
296 conn
297 |> put_status(404)
298 |> json("Not found")
299 end
300
301 def errors(conn, _e) do
302 conn
303 |> put_status(500)
304 |> json("error")
305 end
306
307 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
308 with actor <- conn.params["actor"],
309 true <- is_binary(actor) do
310 Pleroma.Instances.set_reachable(actor)
311 end
312
313 conn
314 end
315 end