Merge branch 'update-mastofe/glitch-soc-2019-02-10' 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.User
10 alias Pleroma.Object
11 alias Pleroma.Web.ActivityPub.ObjectView
12 alias Pleroma.Web.ActivityPub.UserView
13 alias Pleroma.Web.ActivityPub.ActivityPub
14 alias Pleroma.Web.ActivityPub.Relay
15 alias Pleroma.Web.ActivityPub.Transmogrifier
16 alias Pleroma.Web.ActivityPub.Utils
17 alias Pleroma.Web.Federator
18
19 require Logger
20
21 action_fallback(:errors)
22
23 plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
24 plug(:set_requester_reachable when action in [:inbox])
25 plug(:relay_active? when action in [:relay])
26
27 def relay_active?(conn, _) do
28 if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do
29 conn
30 else
31 conn
32 |> put_status(404)
33 |> json(%{error: "not found"})
34 |> halt
35 end
36 end
37
38 def user(conn, %{"nickname" => nickname}) do
39 with %User{} = user <- User.get_cached_by_nickname(nickname),
40 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
41 conn
42 |> put_resp_header("content-type", "application/activity+json")
43 |> json(UserView.render("user.json", %{user: user}))
44 else
45 nil -> {:error, :not_found}
46 end
47 end
48
49 def object(conn, %{"uuid" => uuid}) do
50 with ap_id <- o_status_url(conn, :object, uuid),
51 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
52 {_, true} <- {:public?, ActivityPub.is_public?(object)} do
53 conn
54 |> put_resp_header("content-type", "application/activity+json")
55 |> json(ObjectView.render("object.json", %{object: object}))
56 else
57 {:public?, false} ->
58 {:error, :not_found}
59 end
60 end
61
62 def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
63 with ap_id <- o_status_url(conn, :object, uuid),
64 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
65 {_, true} <- {:public?, ActivityPub.is_public?(object)},
66 likes <- Utils.get_object_likes(object) do
67 {page, _} = Integer.parse(page)
68
69 conn
70 |> put_resp_header("content-type", "application/activity+json")
71 |> json(ObjectView.render("likes.json", ap_id, likes, page))
72 else
73 {:public?, false} ->
74 {:error, :not_found}
75 end
76 end
77
78 def object_likes(conn, %{"uuid" => uuid}) do
79 with ap_id <- o_status_url(conn, :object, uuid),
80 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
81 {_, true} <- {:public?, ActivityPub.is_public?(object)},
82 likes <- Utils.get_object_likes(object) do
83 conn
84 |> put_resp_header("content-type", "application/activity+json")
85 |> json(ObjectView.render("likes.json", ap_id, likes))
86 else
87 {:public?, false} ->
88 {:error, :not_found}
89 end
90 end
91
92 def activity(conn, %{"uuid" => uuid}) do
93 with ap_id <- o_status_url(conn, :activity, uuid),
94 %Activity{} = activity <- Activity.normalize(ap_id),
95 {_, true} <- {:public?, ActivityPub.is_public?(activity)} do
96 conn
97 |> put_resp_header("content-type", "application/activity+json")
98 |> json(ObjectView.render("object.json", %{object: activity}))
99 else
100 {:public?, false} ->
101 {:error, :not_found}
102 end
103 end
104
105 def following(conn, %{"nickname" => nickname, "page" => page}) do
106 with %User{} = user <- User.get_cached_by_nickname(nickname),
107 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
108 {page, _} = Integer.parse(page)
109
110 conn
111 |> put_resp_header("content-type", "application/activity+json")
112 |> json(UserView.render("following.json", %{user: user, page: page}))
113 end
114 end
115
116 def following(conn, %{"nickname" => nickname}) do
117 with %User{} = user <- User.get_cached_by_nickname(nickname),
118 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
119 conn
120 |> put_resp_header("content-type", "application/activity+json")
121 |> json(UserView.render("following.json", %{user: user}))
122 end
123 end
124
125 def followers(conn, %{"nickname" => nickname, "page" => page}) do
126 with %User{} = user <- User.get_cached_by_nickname(nickname),
127 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
128 {page, _} = Integer.parse(page)
129
130 conn
131 |> put_resp_header("content-type", "application/activity+json")
132 |> json(UserView.render("followers.json", %{user: user, page: page}))
133 end
134 end
135
136 def followers(conn, %{"nickname" => nickname}) do
137 with %User{} = user <- User.get_cached_by_nickname(nickname),
138 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
139 conn
140 |> put_resp_header("content-type", "application/activity+json")
141 |> json(UserView.render("followers.json", %{user: user}))
142 end
143 end
144
145 def outbox(conn, %{"nickname" => nickname} = params) do
146 with %User{} = user <- User.get_cached_by_nickname(nickname),
147 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
148 conn
149 |> put_resp_header("content-type", "application/activity+json")
150 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
151 end
152 end
153
154 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
155 with %User{} = user <- User.get_cached_by_nickname(nickname),
156 true <- Utils.recipient_in_message(user.ap_id, params),
157 params <- Utils.maybe_splice_recipient(user.ap_id, params) do
158 Federator.enqueue(:incoming_ap_doc, params)
159 json(conn, "ok")
160 end
161 end
162
163 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
164 Federator.enqueue(:incoming_ap_doc, params)
165 json(conn, "ok")
166 end
167
168 # only accept relayed Creates
169 def inbox(conn, %{"type" => "Create"} = params) do
170 Logger.info(
171 "Signature missing or not from author, relayed Create message, fetching object from source"
172 )
173
174 ActivityPub.fetch_object_from_id(params["object"]["id"])
175
176 json(conn, "ok")
177 end
178
179 def inbox(conn, params) do
180 headers = Enum.into(conn.req_headers, %{})
181
182 if String.contains?(headers["signature"], params["actor"]) do
183 Logger.info(
184 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
185 )
186
187 Logger.info(inspect(conn.req_headers))
188 end
189
190 json(conn, "error")
191 end
192
193 def relay(conn, _params) do
194 with %User{} = user <- Relay.get_actor(),
195 {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
196 conn
197 |> put_resp_header("content-type", "application/activity+json")
198 |> json(UserView.render("user.json", %{user: user}))
199 else
200 nil -> {:error, :not_found}
201 end
202 end
203
204 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
205 conn
206 |> put_resp_header("content-type", "application/activity+json")
207 |> json(UserView.render("user.json", %{user: user}))
208 end
209
210 def whoami(_conn, _params), do: {:error, :not_found}
211
212 def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
213 if nickname == user.nickname do
214 conn
215 |> put_resp_header("content-type", "application/activity+json")
216 |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
217 else
218 conn
219 |> put_status(:forbidden)
220 |> json("can't read inbox of #{nickname} as #{user.nickname}")
221 end
222 end
223
224 def handle_user_activity(user, %{"type" => "Create"} = params) do
225 object =
226 params["object"]
227 |> Map.merge(Map.take(params, ["to", "cc"]))
228 |> Map.put("attributedTo", user.ap_id())
229 |> Transmogrifier.fix_object()
230
231 ActivityPub.create(%{
232 to: params["to"],
233 actor: user,
234 context: object["context"],
235 object: object,
236 additional: Map.take(params, ["cc"])
237 })
238 end
239
240 def handle_user_activity(user, %{"type" => "Delete"} = params) do
241 with %Object{} = object <- Object.normalize(params["object"]),
242 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
243 {:ok, delete} <- ActivityPub.delete(object) do
244 {:ok, delete}
245 else
246 _ -> {:error, "Can't delete object"}
247 end
248 end
249
250 def handle_user_activity(user, %{"type" => "Like"} = params) do
251 with %Object{} = object <- Object.normalize(params["object"]),
252 {:ok, activity, _object} <- ActivityPub.like(user, object) do
253 {:ok, activity}
254 else
255 _ -> {:error, "Can't like object"}
256 end
257 end
258
259 def handle_user_activity(_, _) do
260 {:error, "Unhandled activity type"}
261 end
262
263 def update_outbox(
264 %{assigns: %{user: user}} = conn,
265 %{"nickname" => nickname} = params
266 ) do
267 if nickname == user.nickname do
268 actor = user.ap_id()
269
270 params =
271 params
272 |> Map.drop(["id"])
273 |> Map.put("actor", actor)
274 |> Transmogrifier.fix_addressing()
275
276 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
277 conn
278 |> put_status(:created)
279 |> put_resp_header("location", activity.data["id"])
280 |> json(activity.data)
281 else
282 {:error, message} ->
283 conn
284 |> put_status(:bad_request)
285 |> json(message)
286 end
287 else
288 conn
289 |> put_status(:forbidden)
290 |> json("can't update outbox of #{nickname} as #{user.nickname}")
291 end
292 end
293
294 def errors(conn, {:error, :not_found}) do
295 conn
296 |> put_status(404)
297 |> json("Not found")
298 end
299
300 def errors(conn, _e) do
301 conn
302 |> put_status(500)
303 |> json("error")
304 end
305
306 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
307 with actor <- conn.params["actor"],
308 true <- is_binary(actor) do
309 Pleroma.Instances.set_reachable(actor)
310 end
311
312 conn
313 end
314 end