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.

554 lines
17KB

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