Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into feature/digest...
[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{} = recipient <- User.get_cached_by_nickname(nickname),
157 %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 ActivityPub.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, "error")
193 end
194
195 def relay(conn, _params) do
196 with %User{} = user <- Relay.get_actor(),
197 {:ok, user} <- Pleroma.Web.WebFinger.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 conn
221 |> put_status(:forbidden)
222 |> json("can't read inbox of #{nickname} as #{user.nickname}")
223 end
224 end
225
226 def handle_user_activity(user, %{"type" => "Create"} = params) do
227 object =
228 params["object"]
229 |> Map.merge(Map.take(params, ["to", "cc"]))
230 |> Map.put("attributedTo", user.ap_id())
231 |> Transmogrifier.fix_object()
232
233 ActivityPub.create(%{
234 to: params["to"],
235 actor: user,
236 context: object["context"],
237 object: object,
238 additional: Map.take(params, ["cc"])
239 })
240 end
241
242 def handle_user_activity(user, %{"type" => "Delete"} = params) do
243 with %Object{} = object <- Object.normalize(params["object"]),
244 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
245 {:ok, delete} <- ActivityPub.delete(object) do
246 {:ok, delete}
247 else
248 _ -> {:error, "Can't delete object"}
249 end
250 end
251
252 def handle_user_activity(user, %{"type" => "Like"} = params) do
253 with %Object{} = object <- Object.normalize(params["object"]),
254 {:ok, activity, _object} <- ActivityPub.like(user, object) do
255 {:ok, activity}
256 else
257 _ -> {:error, "Can't like object"}
258 end
259 end
260
261 def handle_user_activity(_, _) do
262 {:error, "Unhandled activity type"}
263 end
264
265 def update_outbox(
266 %{assigns: %{user: user}} = conn,
267 %{"nickname" => nickname} = params
268 ) do
269 if nickname == user.nickname do
270 actor = user.ap_id()
271
272 params =
273 params
274 |> Map.drop(["id"])
275 |> Map.put("actor", actor)
276 |> Transmogrifier.fix_addressing()
277
278 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
279 conn
280 |> put_status(:created)
281 |> put_resp_header("location", activity.data["id"])
282 |> json(activity.data)
283 else
284 {:error, message} ->
285 conn
286 |> put_status(:bad_request)
287 |> json(message)
288 end
289 else
290 conn
291 |> put_status(:forbidden)
292 |> json("can't update outbox of #{nickname} as #{user.nickname}")
293 end
294 end
295
296 def errors(conn, {:error, :not_found}) do
297 conn
298 |> put_status(404)
299 |> json("Not found")
300 end
301
302 def errors(conn, _e) do
303 conn
304 |> put_status(500)
305 |> json("error")
306 end
307
308 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
309 with actor <- conn.params["actor"],
310 true <- is_binary(actor) do
311 Pleroma.Instances.set_reachable(actor)
312 end
313
314 conn
315 end
316 end