1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.ActivityPub.ActivityPubController do
6 use Pleroma.Web, :controller
11 alias Pleroma.Object.Fetcher
13 alias Pleroma.Web.ActivityPub.ActivityPub
14 alias Pleroma.Web.ActivityPub.InternalFetchActor
15 alias Pleroma.Web.ActivityPub.ObjectView
16 alias Pleroma.Web.ActivityPub.Relay
17 alias Pleroma.Web.ActivityPub.Transmogrifier
18 alias Pleroma.Web.ActivityPub.UserView
19 alias Pleroma.Web.ActivityPub.Utils
20 alias Pleroma.Web.ActivityPub.Visibility
21 alias Pleroma.Web.Federator
25 action_fallback(:errors)
29 [query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
30 when action in [:activity, :object]
33 plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
34 plug(:set_requester_reachable when action in [:inbox])
35 plug(:relay_active? when action in [:relay])
37 def relay_active?(conn, _) do
38 if Pleroma.Config.get([:instance, :allow_relay]) do
42 |> render_error(:not_found, "not found")
47 def user(conn, %{"nickname" => nickname}) do
48 with %User{} = user <- User.get_cached_by_nickname(nickname),
49 {:ok, user} <- User.ensure_keys_present(user) do
51 |> put_resp_content_type("application/activity+json")
52 |> json(UserView.render("user.json", %{user: user}))
54 nil -> {:error, :not_found}
58 def object(conn, %{"uuid" => uuid}) do
59 with ap_id <- o_status_url(conn, :object, uuid),
60 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
61 {_, true} <- {:public?, Visibility.is_public?(object)} do
63 |> assign(:tracking_fun_data, object.id)
64 |> set_cache_ttl_for(object)
65 |> put_resp_content_type("application/activity+json")
66 |> put_view(ObjectView)
67 |> render("object.json", object: object)
74 def track_object_fetch(conn, nil), do: conn
76 def track_object_fetch(conn, object_id) do
77 with %{assigns: %{user: %User{id: user_id}}} <- conn do
78 Delivery.create(object_id, user_id)
84 def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
85 with ap_id <- o_status_url(conn, :object, uuid),
86 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
87 {_, true} <- {:public?, Visibility.is_public?(object)},
88 likes <- Utils.get_object_likes(object) do
89 {page, _} = Integer.parse(page)
92 |> put_resp_content_type("application/activity+json")
93 |> json(ObjectView.render("likes.json", ap_id, likes, page))
100 def object_likes(conn, %{"uuid" => uuid}) do
101 with ap_id <- o_status_url(conn, :object, uuid),
102 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
103 {_, true} <- {:public?, Visibility.is_public?(object)},
104 likes <- Utils.get_object_likes(object) do
106 |> put_resp_content_type("application/activity+json")
107 |> json(ObjectView.render("likes.json", ap_id, likes))
114 def activity(conn, %{"uuid" => uuid}) do
115 with ap_id <- o_status_url(conn, :activity, uuid),
116 %Activity{} = activity <- Activity.normalize(ap_id),
117 {_, true} <- {:public?, Visibility.is_public?(activity)} do
119 |> maybe_set_tracking_data(activity)
120 |> set_cache_ttl_for(activity)
121 |> put_resp_content_type("application/activity+json")
122 |> put_view(ObjectView)
123 |> render("object.json", object: activity)
125 {:public?, false} -> {:error, :not_found}
126 nil -> {:error, :not_found}
130 defp maybe_set_tracking_data(conn, %Activity{data: %{"type" => "Create"}} = activity) do
131 object_id = Object.normalize(activity).id
132 assign(conn, :tracking_fun_data, object_id)
135 defp maybe_set_tracking_data(conn, _activity), do: conn
137 defp set_cache_ttl_for(conn, %Activity{object: object}) do
138 set_cache_ttl_for(conn, object)
141 defp set_cache_ttl_for(conn, entity) do
144 %Object{data: %{"type" => "Question"}} ->
145 Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
148 Pleroma.Config.get([:web_cache_ttl, :activity_pub])
154 assign(conn, :cache_ttl, ttl)
157 # GET /relay/following
158 def following(%{assigns: %{relay: true}} = conn, _params) do
160 |> put_resp_content_type("application/activity+json")
161 |> json(UserView.render("following.json", %{user: Relay.get_actor()}))
164 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
165 with %User{} = user <- User.get_cached_by_nickname(nickname),
166 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
167 {:show_follows, true} <-
168 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
169 {page, _} = Integer.parse(page)
172 |> put_resp_content_type("application/activity+json")
173 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
175 {:show_follows, _} ->
177 |> put_resp_content_type("application/activity+json")
178 |> send_resp(403, "")
182 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
183 with %User{} = user <- User.get_cached_by_nickname(nickname),
184 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
186 |> put_resp_content_type("application/activity+json")
187 |> json(UserView.render("following.json", %{user: user, for: for_user}))
191 # GET /relay/followers
192 def followers(%{assigns: %{relay: true}} = conn, _params) do
194 |> put_resp_content_type("application/activity+json")
195 |> json(UserView.render("followers.json", %{user: Relay.get_actor()}))
198 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
199 with %User{} = user <- User.get_cached_by_nickname(nickname),
200 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
201 {:show_followers, true} <-
202 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
203 {page, _} = Integer.parse(page)
206 |> put_resp_content_type("application/activity+json")
207 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
209 {:show_followers, _} ->
211 |> put_resp_content_type("application/activity+json")
212 |> send_resp(403, "")
216 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
217 with %User{} = user <- User.get_cached_by_nickname(nickname),
218 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
220 |> put_resp_content_type("application/activity+json")
221 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
225 def outbox(conn, %{"nickname" => nickname} = params) do
226 with %User{} = user <- User.get_cached_by_nickname(nickname),
227 {:ok, user} <- User.ensure_keys_present(user) do
229 |> put_resp_content_type("application/activity+json")
230 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
234 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
235 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
236 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
237 true <- Utils.recipient_in_message(recipient, actor, params),
238 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
239 Federator.incoming_ap_doc(params)
244 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
245 Federator.incoming_ap_doc(params)
249 # only accept relayed Creates
250 def inbox(conn, %{"type" => "Create"} = params) do
252 "Signature missing or not from author, relayed Create message, fetching object from source"
255 Fetcher.fetch_object_from_id(params["object"]["id"])
260 def inbox(conn, params) do
261 headers = Enum.into(conn.req_headers, %{})
263 if String.contains?(headers["signature"], params["actor"]) do
265 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
268 Logger.info(inspect(conn.req_headers))
271 json(conn, dgettext("errors", "error"))
274 defp represent_service_actor(%User{} = user, conn) do
275 with {:ok, user} <- User.ensure_keys_present(user) do
277 |> put_resp_content_type("application/activity+json")
278 |> json(UserView.render("user.json", %{user: user}))
280 nil -> {:error, :not_found}
284 defp represent_service_actor(nil, _), do: {:error, :not_found}
286 def relay(conn, _params) do
288 |> represent_service_actor(conn)
291 def internal_fetch(conn, _params) do
292 InternalFetchActor.get_actor()
293 |> represent_service_actor(conn)
296 @doc "Returns the authenticated user's ActivityPub User object or a 404 Not Found if non-authenticated"
297 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
299 |> put_resp_content_type("application/activity+json")
300 |> json(UserView.render("user.json", %{user: user}))
303 def whoami(_conn, _params), do: {:error, :not_found}
306 %{assigns: %{user: %{nickname: nickname} = user}} = conn,
307 %{"nickname" => nickname} = params
310 |> put_resp_content_type("application/activity+json")
311 |> put_view(UserView)
312 |> render("inbox.json", user: user, max_id: params["max_id"])
315 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
316 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
319 |> put_status(:forbidden)
323 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
324 "nickname" => nickname
327 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
329 as_nickname: as_nickname
333 |> put_status(:forbidden)
337 def handle_user_activity(user, %{"type" => "Create"} = params) do
340 |> Map.merge(Map.take(params, ["to", "cc"]))
341 |> Map.put("attributedTo", user.ap_id())
342 |> Transmogrifier.fix_object()
344 ActivityPub.create(%{
347 context: object["context"],
349 additional: Map.take(params, ["cc"])
353 def handle_user_activity(user, %{"type" => "Delete"} = params) do
354 with %Object{} = object <- Object.normalize(params["object"]),
355 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
356 {:ok, delete} <- ActivityPub.delete(object) do
359 _ -> {:error, dgettext("errors", "Can't delete object")}
363 def handle_user_activity(user, %{"type" => "Like"} = params) do
364 with %Object{} = object <- Object.normalize(params["object"]),
365 {:ok, activity, _object} <- ActivityPub.like(user, object) do
368 _ -> {:error, dgettext("errors", "Can't like object")}
372 def handle_user_activity(_, _) do
373 {:error, dgettext("errors", "Unhandled activity type")}
377 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
378 %{"nickname" => nickname} = params
385 |> Map.put("actor", actor)
386 |> Transmogrifier.fix_addressing()
388 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
390 |> put_status(:created)
391 |> put_resp_header("location", activity.data["id"])
392 |> json(activity.data)
396 |> put_status(:bad_request)
401 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
403 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
405 as_nickname: user.nickname
409 |> put_status(:forbidden)
413 def errors(conn, {:error, :not_found}) do
415 |> put_status(:not_found)
416 |> json(dgettext("errors", "Not found"))
419 def errors(conn, _e) do
421 |> put_status(:internal_server_error)
422 |> json(dgettext("errors", "error"))
425 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
426 with actor <- conn.params["actor"],
427 true <- is_binary(actor) do
428 Pleroma.Instances.set_reachable(actor)
434 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
435 {:ok, new_user} = User.ensure_keys_present(user)
438 if new_user != user and match?(%User{}, for_user) do
439 User.get_cached_by_nickname(for_user.nickname)
447 # TODO: Add support for "object" field
449 Endpoint based on <https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload>
452 - (required) `file`: data of the media
453 - (optionnal) `description`: description of the media, intended for accessibility
456 - HTTP Code: 201 Created
457 - HTTP Body: ActivityPub object to be inserted into another's `attachment` field
459 def upload_media(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
460 with {:ok, object} <-
463 actor: User.ap_id(user),
464 description: Map.get(data, "description")
466 Logger.debug(inspect(object))
469 |> put_status(:created)