Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

561 lines
17KB

  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.ActivityPub.ActivityPubController do
  5. use Pleroma.Web, :controller
  6. alias Pleroma.Activity
  7. alias Pleroma.Delivery
  8. alias Pleroma.Object
  9. alias Pleroma.Object.Fetcher
  10. alias Pleroma.User
  11. alias Pleroma.Web.ActivityPub.ActivityPub
  12. alias Pleroma.Web.ActivityPub.InternalFetchActor
  13. alias Pleroma.Web.ActivityPub.ObjectView
  14. alias Pleroma.Web.ActivityPub.Pipeline
  15. alias Pleroma.Web.ActivityPub.Relay
  16. alias Pleroma.Web.ActivityPub.Transmogrifier
  17. alias Pleroma.Web.ActivityPub.UserView
  18. alias Pleroma.Web.ActivityPub.Utils
  19. alias Pleroma.Web.ActivityPub.Visibility
  20. alias Pleroma.Web.ControllerHelper
  21. alias Pleroma.Web.Endpoint
  22. alias Pleroma.Web.Federator
  23. alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug
  24. alias Pleroma.Web.Plugs.FederatingPlug
  25. require Logger
  26. action_fallback(:errors)
  27. @federating_only_actions [:internal_fetch, :relay, :relay_following, :relay_followers]
  28. plug(FederatingPlug when action in @federating_only_actions)
  29. plug(
  30. EnsureAuthenticatedPlug,
  31. [unless_func: &FederatingPlug.federating?/1] when action not in @federating_only_actions
  32. )
  33. # Note: :following and :followers must be served even without authentication (as via :api)
  34. plug(
  35. EnsureAuthenticatedPlug
  36. when action in [:read_inbox, :update_outbox, :whoami, :upload_media]
  37. )
  38. plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:upload_media])
  39. plug(
  40. Pleroma.Web.Plugs.Cache,
  41. [query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
  42. when action in [:activity, :object]
  43. )
  44. plug(:set_requester_reachable when action in [:inbox])
  45. plug(:relay_active? when action in [:relay])
  46. defp relay_active?(conn, _) do
  47. if Pleroma.Config.get([:instance, :allow_relay]) do
  48. conn
  49. else
  50. conn
  51. |> render_error(:not_found, "not found")
  52. |> halt()
  53. end
  54. end
  55. def user(conn, %{"nickname" => nickname}) do
  56. with %User{local: true} = user <- User.get_cached_by_nickname(nickname),
  57. {:ok, user} <- User.ensure_keys_present(user) do
  58. conn
  59. |> put_resp_content_type("application/activity+json")
  60. |> put_view(UserView)
  61. |> render("user.json", %{user: user})
  62. else
  63. nil -> {:error, :not_found}
  64. %{local: false} -> {:error, :not_found}
  65. end
  66. end
  67. def object(%{assigns: assigns} = conn, _) do
  68. with ap_id <- Endpoint.url() <> conn.request_path,
  69. %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
  70. user <- Map.get(assigns, :user, nil),
  71. {_, true} <- {:visible?, Visibility.visible_for_user?(object, user)} do
  72. conn
  73. |> assign(:tracking_fun_data, object.id)
  74. |> set_cache_ttl_for(object)
  75. |> put_resp_content_type("application/activity+json")
  76. |> put_view(ObjectView)
  77. |> render("object.json", object: object)
  78. else
  79. {:visible?, false} -> {:error, :not_found}
  80. nil -> {:error, :not_found}
  81. end
  82. end
  83. def track_object_fetch(conn, nil), do: conn
  84. def track_object_fetch(conn, object_id) do
  85. with %{assigns: %{user: %User{id: user_id}}} <- conn do
  86. Delivery.create(object_id, user_id)
  87. end
  88. conn
  89. end
  90. def activity(%{assigns: assigns} = conn, _) do
  91. with ap_id <- Endpoint.url() <> conn.request_path,
  92. %Activity{} = activity <- Activity.normalize(ap_id),
  93. {_, true} <- {:local?, activity.local},
  94. user <- Map.get(assigns, :user, nil),
  95. {_, true} <- {:visible?, Visibility.visible_for_user?(activity, user)} do
  96. conn
  97. |> maybe_set_tracking_data(activity)
  98. |> set_cache_ttl_for(activity)
  99. |> put_resp_content_type("application/activity+json")
  100. |> put_view(ObjectView)
  101. |> render("object.json", object: activity)
  102. else
  103. {:visible?, false} -> {:error, :not_found}
  104. {:local?, false} -> {:error, :not_found}
  105. nil -> {:error, :not_found}
  106. end
  107. end
  108. defp maybe_set_tracking_data(conn, %Activity{data: %{"type" => "Create"}} = activity) do
  109. object_id = Object.normalize(activity, fetch: false).id
  110. assign(conn, :tracking_fun_data, object_id)
  111. end
  112. defp maybe_set_tracking_data(conn, _activity), do: conn
  113. defp set_cache_ttl_for(conn, %Activity{object: object}) do
  114. set_cache_ttl_for(conn, object)
  115. end
  116. defp set_cache_ttl_for(conn, entity) do
  117. ttl =
  118. case entity do
  119. %Object{data: %{"type" => "Question"}} ->
  120. Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
  121. %Object{} ->
  122. Pleroma.Config.get([:web_cache_ttl, :activity_pub])
  123. _ ->
  124. nil
  125. end
  126. assign(conn, :cache_ttl, ttl)
  127. end
  128. # GET /relay/following
  129. def relay_following(conn, _params) do
  130. with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
  131. conn
  132. |> put_resp_content_type("application/activity+json")
  133. |> put_view(UserView)
  134. |> render("following.json", %{user: Relay.get_actor()})
  135. end
  136. end
  137. def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
  138. with %User{} = user <- User.get_cached_by_nickname(nickname),
  139. {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
  140. {:show_follows, true} <-
  141. {:show_follows, (for_user && for_user == user) || !user.hide_follows} do
  142. {page, _} = Integer.parse(page)
  143. conn
  144. |> put_resp_content_type("application/activity+json")
  145. |> put_view(UserView)
  146. |> render("following.json", %{user: user, page: page, for: for_user})
  147. else
  148. {:show_follows, _} ->
  149. conn
  150. |> put_resp_content_type("application/activity+json")
  151. |> send_resp(403, "")
  152. end
  153. end
  154. def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
  155. with %User{} = user <- User.get_cached_by_nickname(nickname),
  156. {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
  157. conn
  158. |> put_resp_content_type("application/activity+json")
  159. |> put_view(UserView)
  160. |> render("following.json", %{user: user, for: for_user})
  161. end
  162. end
  163. # GET /relay/followers
  164. def relay_followers(conn, _params) do
  165. with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
  166. conn
  167. |> put_resp_content_type("application/activity+json")
  168. |> put_view(UserView)
  169. |> render("followers.json", %{user: Relay.get_actor()})
  170. end
  171. end
  172. def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
  173. with %User{} = user <- User.get_cached_by_nickname(nickname),
  174. {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
  175. {:show_followers, true} <-
  176. {:show_followers, (for_user && for_user == user) || !user.hide_followers} do
  177. {page, _} = Integer.parse(page)
  178. conn
  179. |> put_resp_content_type("application/activity+json")
  180. |> put_view(UserView)
  181. |> render("followers.json", %{user: user, page: page, for: for_user})
  182. else
  183. {:show_followers, _} ->
  184. conn
  185. |> put_resp_content_type("application/activity+json")
  186. |> send_resp(403, "")
  187. end
  188. end
  189. def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
  190. with %User{} = user <- User.get_cached_by_nickname(nickname),
  191. {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
  192. conn
  193. |> put_resp_content_type("application/activity+json")
  194. |> put_view(UserView)
  195. |> render("followers.json", %{user: user, for: for_user})
  196. end
  197. end
  198. def outbox(
  199. %{assigns: %{user: for_user}} = conn,
  200. %{"nickname" => nickname, "page" => page?} = params
  201. )
  202. when page? in [true, "true"] do
  203. with %User{} = user <- User.get_cached_by_nickname(nickname),
  204. {:ok, user} <- User.ensure_keys_present(user) do
  205. # "include_poll_votes" is a hack because postgres generates inefficient
  206. # queries when filtering by 'Answer', poll votes will be hidden by the
  207. # visibility filter in this case anyway
  208. params =
  209. params
  210. |> Map.drop(["nickname", "page"])
  211. |> Map.put("include_poll_votes", true)
  212. |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
  213. activities = ActivityPub.fetch_user_activities(user, for_user, params)
  214. conn
  215. |> put_resp_content_type("application/activity+json")
  216. |> put_view(UserView)
  217. |> render("activity_collection_page.json", %{
  218. activities: activities,
  219. pagination: ControllerHelper.get_pagination_fields(conn, activities),
  220. iri: "#{user.ap_id}/outbox"
  221. })
  222. end
  223. end
  224. def outbox(conn, %{"nickname" => nickname}) do
  225. with %User{} = user <- User.get_cached_by_nickname(nickname),
  226. {:ok, user} <- User.ensure_keys_present(user) do
  227. conn
  228. |> put_resp_content_type("application/activity+json")
  229. |> put_view(UserView)
  230. |> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"})
  231. end
  232. end
  233. def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
  234. with %User{} = recipient <- User.get_cached_by_nickname(nickname),
  235. {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
  236. true <- Utils.recipient_in_message(recipient, actor, params),
  237. params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
  238. Federator.incoming_ap_doc(params)
  239. json(conn, "ok")
  240. end
  241. end
  242. def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
  243. Federator.incoming_ap_doc(params)
  244. json(conn, "ok")
  245. end
  246. # POST /relay/inbox -or- POST /internal/fetch/inbox
  247. def inbox(conn, params) do
  248. if params["type"] == "Create" && FederatingPlug.federating?() do
  249. post_inbox_relayed_create(conn, params)
  250. else
  251. post_inbox_fallback(conn, params)
  252. end
  253. end
  254. defp post_inbox_relayed_create(conn, params) do
  255. Logger.debug(
  256. "Signature missing or not from author, relayed Create message, fetching object from source"
  257. )
  258. Fetcher.fetch_object_from_id(params["object"]["id"])
  259. json(conn, "ok")
  260. end
  261. defp post_inbox_fallback(conn, params) do
  262. headers = Enum.into(conn.req_headers, %{})
  263. if headers["signature"] && params["actor"] &&
  264. String.contains?(headers["signature"], params["actor"]) do
  265. Logger.debug(
  266. "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
  267. )
  268. Logger.debug(inspect(conn.req_headers))
  269. end
  270. conn
  271. |> put_status(:bad_request)
  272. |> json(dgettext("errors", "error"))
  273. end
  274. defp represent_service_actor(%User{} = user, conn) do
  275. with {:ok, user} <- User.ensure_keys_present(user) do
  276. conn
  277. |> put_resp_content_type("application/activity+json")
  278. |> put_view(UserView)
  279. |> render("user.json", %{user: user})
  280. else
  281. nil -> {:error, :not_found}
  282. end
  283. end
  284. defp represent_service_actor(nil, _), do: {:error, :not_found}
  285. def relay(conn, _params) do
  286. Relay.get_actor()
  287. |> represent_service_actor(conn)
  288. end
  289. def internal_fetch(conn, _params) do
  290. InternalFetchActor.get_actor()
  291. |> represent_service_actor(conn)
  292. end
  293. @doc "Returns the authenticated user's ActivityPub User object or a 404 Not Found if non-authenticated"
  294. def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
  295. conn
  296. |> put_resp_content_type("application/activity+json")
  297. |> put_view(UserView)
  298. |> render("user.json", %{user: user})
  299. end
  300. def read_inbox(
  301. %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
  302. %{"nickname" => nickname, "page" => page?} = params
  303. )
  304. when page? in [true, "true"] do
  305. params =
  306. params
  307. |> Map.drop(["nickname", "page"])
  308. |> Map.put("blocking_user", user)
  309. |> Map.put("user", user)
  310. |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
  311. activities =
  312. [user.ap_id | User.following(user)]
  313. |> ActivityPub.fetch_activities(params)
  314. |> Enum.reverse()
  315. conn
  316. |> put_resp_content_type("application/activity+json")
  317. |> put_view(UserView)
  318. |> render("activity_collection_page.json", %{
  319. activities: activities,
  320. pagination: ControllerHelper.get_pagination_fields(conn, activities),
  321. iri: "#{user.ap_id}/inbox"
  322. })
  323. end
  324. def read_inbox(%{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{
  325. "nickname" => nickname
  326. }) do
  327. with {:ok, user} <- User.ensure_keys_present(user) do
  328. conn
  329. |> put_resp_content_type("application/activity+json")
  330. |> put_view(UserView)
  331. |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
  332. end
  333. end
  334. def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{
  335. "nickname" => nickname
  336. }) do
  337. err =
  338. dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
  339. nickname: nickname,
  340. as_nickname: as_nickname
  341. )
  342. conn
  343. |> put_status(:forbidden)
  344. |> json(err)
  345. end
  346. defp fix_user_message(%User{ap_id: actor}, %{"type" => "Create", "object" => object} = activity)
  347. when is_map(object) do
  348. length =
  349. [object["content"], object["summary"], object["name"]]
  350. |> Enum.filter(&is_binary(&1))
  351. |> Enum.join("")
  352. |> String.length()
  353. limit = Pleroma.Config.get([:instance, :limit])
  354. if length < limit do
  355. object =
  356. object
  357. |> Transmogrifier.strip_internal_fields()
  358. |> Map.put("attributedTo", actor)
  359. |> Map.put("actor", actor)
  360. |> Map.put("id", Utils.generate_object_id())
  361. {:ok, Map.put(activity, "object", object)}
  362. else
  363. {:error,
  364. dgettext(
  365. "errors",
  366. "Character limit (%{limit} characters) exceeded, contains %{length} characters",
  367. limit: limit,
  368. length: length
  369. )}
  370. end
  371. end
  372. defp fix_user_message(
  373. %User{ap_id: actor} = user,
  374. %{"type" => "Delete", "object" => object} = activity
  375. ) do
  376. with {_, %Object{data: object_data}} <- {:normalize, Object.normalize(object, fetch: false)},
  377. {_, true} <- {:permission, user.is_moderator || actor == object_data["actor"]} do
  378. {:ok, activity}
  379. else
  380. {:normalize, _} ->
  381. {:error, "No such object found"}
  382. {:permission, _} ->
  383. {:forbidden, "You can't delete this object"}
  384. end
  385. end
  386. defp fix_user_message(%User{}, activity) do
  387. {:ok, activity}
  388. end
  389. def update_outbox(
  390. %{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn,
  391. %{"nickname" => nickname} = params
  392. ) do
  393. params =
  394. params
  395. |> Map.drop(["nickname"])
  396. |> Map.put("id", Utils.generate_activity_id())
  397. |> Map.put("actor", actor)
  398. with {:ok, params} <- fix_user_message(user, params),
  399. {:ok, activity, _} <- Pipeline.common_pipeline(params, local: true),
  400. %Activity{data: activity_data} <- Activity.normalize(activity) do
  401. conn
  402. |> put_status(:created)
  403. |> put_resp_header("location", activity_data["id"])
  404. |> json(activity_data)
  405. else
  406. {:forbidden, message} ->
  407. conn
  408. |> put_status(:forbidden)
  409. |> json(message)
  410. {:error, message} ->
  411. conn
  412. |> put_status(:bad_request)
  413. |> json(message)
  414. e ->
  415. Logger.warn(fn -> "AP C2S: #{inspect(e)}" end)
  416. conn
  417. |> put_status(:bad_request)
  418. |> json("Bad Request")
  419. end
  420. end
  421. def update_outbox(%{assigns: %{user: %User{} = user}} = conn, %{"nickname" => nickname}) do
  422. err =
  423. dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
  424. nickname: nickname,
  425. as_nickname: user.nickname
  426. )
  427. conn
  428. |> put_status(:forbidden)
  429. |> json(err)
  430. end
  431. defp errors(conn, {:error, :not_found}) do
  432. conn
  433. |> put_status(:not_found)
  434. |> json(dgettext("errors", "Not found"))
  435. end
  436. defp errors(conn, _e) do
  437. conn
  438. |> put_status(:internal_server_error)
  439. |> json(dgettext("errors", "error"))
  440. end
  441. defp set_requester_reachable(%Plug.Conn{} = conn, _) do
  442. with actor <- conn.params["actor"],
  443. true <- is_binary(actor) do
  444. Pleroma.Instances.set_reachable(actor)
  445. end
  446. conn
  447. end
  448. defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
  449. {:ok, new_user} = User.ensure_keys_present(user)
  450. for_user =
  451. if new_user != user and match?(%User{}, for_user) do
  452. User.get_cached_by_nickname(for_user.nickname)
  453. else
  454. for_user
  455. end
  456. {new_user, for_user}
  457. end
  458. def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
  459. with {:ok, object} <-
  460. ActivityPub.upload(
  461. file,
  462. actor: User.ap_id(user),
  463. description: Map.get(data, "description")
  464. ) do
  465. Logger.debug(inspect(object))
  466. conn
  467. |> put_status(:created)
  468. |> json(object.data)
  469. end
  470. end
  471. def pinned(conn, %{"nickname" => nickname}) do
  472. with %User{} = user <- User.get_cached_by_nickname(nickname) do
  473. conn
  474. |> put_resp_header("content-type", "application/activity+json")
  475. |> json(UserView.render("featured.json", %{user: user}))
  476. end
  477. end
  478. end