logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git

activity_pub_controller.ex (16734B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2022 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) do
  57. conn
  58. |> put_resp_content_type("application/activity+json")
  59. |> put_view(UserView)
  60. |> render("user.json", %{user: user})
  61. else
  62. nil -> {:error, :not_found}
  63. %{local: false} -> {:error, :not_found}
  64. end
  65. end
  66. def object(%{assigns: assigns} = conn, _) do
  67. with ap_id <- Endpoint.url() <> conn.request_path,
  68. %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
  69. user <- Map.get(assigns, :user, nil),
  70. {_, true} <- {:visible?, Visibility.visible_for_user?(object, user)} do
  71. conn
  72. |> maybe_skip_cache(user)
  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_skip_cache(user)
  98. |> maybe_set_tracking_data(activity)
  99. |> set_cache_ttl_for(activity)
  100. |> put_resp_content_type("application/activity+json")
  101. |> put_view(ObjectView)
  102. |> render("object.json", object: activity)
  103. else
  104. {:visible?, false} -> {:error, :not_found}
  105. {:local?, false} -> {:error, :not_found}
  106. nil -> {:error, :not_found}
  107. end
  108. end
  109. defp maybe_set_tracking_data(conn, %Activity{data: %{"type" => "Create"}} = activity) do
  110. object_id = Object.normalize(activity, fetch: false).id
  111. assign(conn, :tracking_fun_data, object_id)
  112. end
  113. defp maybe_set_tracking_data(conn, _activity), do: conn
  114. defp set_cache_ttl_for(conn, %Activity{object: object}) do
  115. set_cache_ttl_for(conn, object)
  116. end
  117. defp set_cache_ttl_for(conn, entity) do
  118. ttl =
  119. case entity do
  120. %Object{data: %{"type" => "Question"}} ->
  121. Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
  122. %Object{} ->
  123. Pleroma.Config.get([:web_cache_ttl, :activity_pub])
  124. _ ->
  125. nil
  126. end
  127. assign(conn, :cache_ttl, ttl)
  128. end
  129. def maybe_skip_cache(conn, user) do
  130. if user do
  131. conn
  132. |> assign(:skip_cache, true)
  133. else
  134. conn
  135. end
  136. end
  137. # GET /relay/following
  138. def relay_following(conn, _params) do
  139. with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
  140. conn
  141. |> put_resp_content_type("application/activity+json")
  142. |> put_view(UserView)
  143. |> render("following.json", %{user: Relay.get_actor()})
  144. end
  145. end
  146. def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
  147. with %User{} = user <- User.get_cached_by_nickname(nickname),
  148. {:show_follows, true} <-
  149. {:show_follows, (for_user && for_user == user) || !user.hide_follows} do
  150. {page, _} = Integer.parse(page)
  151. conn
  152. |> put_resp_content_type("application/activity+json")
  153. |> put_view(UserView)
  154. |> render("following.json", %{user: user, page: page, for: for_user})
  155. else
  156. {:show_follows, _} ->
  157. conn
  158. |> put_resp_content_type("application/activity+json")
  159. |> send_resp(403, "")
  160. end
  161. end
  162. def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
  163. with %User{} = user <- User.get_cached_by_nickname(nickname) do
  164. conn
  165. |> put_resp_content_type("application/activity+json")
  166. |> put_view(UserView)
  167. |> render("following.json", %{user: user, for: for_user})
  168. end
  169. end
  170. # GET /relay/followers
  171. def relay_followers(conn, _params) do
  172. with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
  173. conn
  174. |> put_resp_content_type("application/activity+json")
  175. |> put_view(UserView)
  176. |> render("followers.json", %{user: Relay.get_actor()})
  177. end
  178. end
  179. def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
  180. with %User{} = user <- User.get_cached_by_nickname(nickname),
  181. {:show_followers, true} <-
  182. {:show_followers, (for_user && for_user == user) || !user.hide_followers} do
  183. {page, _} = Integer.parse(page)
  184. conn
  185. |> put_resp_content_type("application/activity+json")
  186. |> put_view(UserView)
  187. |> render("followers.json", %{user: user, page: page, for: for_user})
  188. else
  189. {:show_followers, _} ->
  190. conn
  191. |> put_resp_content_type("application/activity+json")
  192. |> send_resp(403, "")
  193. end
  194. end
  195. def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
  196. with %User{} = user <- User.get_cached_by_nickname(nickname) do
  197. conn
  198. |> put_resp_content_type("application/activity+json")
  199. |> put_view(UserView)
  200. |> render("followers.json", %{user: user, for: for_user})
  201. end
  202. end
  203. def outbox(
  204. %{assigns: %{user: for_user}} = conn,
  205. %{"nickname" => nickname, "page" => page?} = params
  206. )
  207. when page? in [true, "true"] do
  208. with %User{} = user <- User.get_cached_by_nickname(nickname) do
  209. # "include_poll_votes" is a hack because postgres generates inefficient
  210. # queries when filtering by 'Answer', poll votes will be hidden by the
  211. # visibility filter in this case anyway
  212. params =
  213. params
  214. |> Map.drop(["nickname", "page"])
  215. |> Map.put("include_poll_votes", true)
  216. |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
  217. activities = ActivityPub.fetch_user_activities(user, for_user, params)
  218. conn
  219. |> put_resp_content_type("application/activity+json")
  220. |> put_view(UserView)
  221. |> render("activity_collection_page.json", %{
  222. activities: activities,
  223. pagination: ControllerHelper.get_pagination_fields(conn, activities),
  224. iri: "#{user.ap_id}/outbox"
  225. })
  226. end
  227. end
  228. def outbox(conn, %{"nickname" => nickname}) do
  229. with %User{} = user <- User.get_cached_by_nickname(nickname) do
  230. conn
  231. |> put_resp_content_type("application/activity+json")
  232. |> put_view(UserView)
  233. |> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"})
  234. end
  235. end
  236. def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
  237. with %User{is_active: true} = recipient <- User.get_cached_by_nickname(nickname),
  238. {:ok, %User{is_active: true} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
  239. true <- Utils.recipient_in_message(recipient, actor, params),
  240. params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
  241. Federator.incoming_ap_doc(params)
  242. json(conn, "ok")
  243. else
  244. _ ->
  245. conn
  246. |> put_status(:bad_request)
  247. |> json("Invalid request.")
  248. end
  249. end
  250. def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
  251. Federator.incoming_ap_doc(params)
  252. json(conn, "ok")
  253. end
  254. def inbox(%{assigns: %{valid_signature: false}, req_headers: req_headers} = conn, params) do
  255. Federator.incoming_ap_doc(%{req_headers: req_headers, params: params})
  256. json(conn, "ok")
  257. end
  258. # POST /relay/inbox -or- POST /internal/fetch/inbox
  259. def inbox(conn, %{"type" => "Create"} = params) do
  260. if FederatingPlug.federating?() do
  261. post_inbox_relayed_create(conn, params)
  262. else
  263. conn
  264. |> put_status(:bad_request)
  265. |> json("Not federating")
  266. end
  267. end
  268. def inbox(conn, _params) do
  269. conn
  270. |> put_status(:bad_request)
  271. |> json("error, missing HTTP Signature")
  272. end
  273. defp post_inbox_relayed_create(conn, params) do
  274. Logger.debug(
  275. "Signature missing or not from author, relayed Create message, fetching object from source"
  276. )
  277. Fetcher.fetch_object_from_id(params["object"]["id"])
  278. json(conn, "ok")
  279. end
  280. defp represent_service_actor(%User{} = user, conn) do
  281. conn
  282. |> put_resp_content_type("application/activity+json")
  283. |> put_view(UserView)
  284. |> render("user.json", %{user: user})
  285. end
  286. defp represent_service_actor(nil, _), do: {:error, :not_found}
  287. def relay(conn, _params) do
  288. Relay.get_actor()
  289. |> represent_service_actor(conn)
  290. end
  291. def internal_fetch(conn, _params) do
  292. InternalFetchActor.get_actor()
  293. |> represent_service_actor(conn)
  294. end
  295. @doc "Returns the authenticated user's ActivityPub User object or a 404 Not Found if non-authenticated"
  296. def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
  297. conn
  298. |> put_resp_content_type("application/activity+json")
  299. |> put_view(UserView)
  300. |> render("user.json", %{user: user})
  301. end
  302. def read_inbox(
  303. %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
  304. %{"nickname" => nickname, "page" => page?} = params
  305. )
  306. when page? in [true, "true"] do
  307. params =
  308. params
  309. |> Map.drop(["nickname", "page"])
  310. |> Map.put("blocking_user", user)
  311. |> Map.put("user", user)
  312. |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
  313. activities =
  314. [user.ap_id | User.following(user)]
  315. |> ActivityPub.fetch_activities(params)
  316. |> Enum.reverse()
  317. conn
  318. |> put_resp_content_type("application/activity+json")
  319. |> put_view(UserView)
  320. |> render("activity_collection_page.json", %{
  321. activities: activities,
  322. pagination: ControllerHelper.get_pagination_fields(conn, activities),
  323. iri: "#{user.ap_id}/inbox"
  324. })
  325. end
  326. def read_inbox(%{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{
  327. "nickname" => nickname
  328. }) do
  329. conn
  330. |> put_resp_content_type("application/activity+json")
  331. |> put_view(UserView)
  332. |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
  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. |> Map.put("published", Utils.make_date())
  362. {:ok, Map.put(activity, "object", object)}
  363. else
  364. {:error,
  365. dgettext(
  366. "errors",
  367. "Character limit (%{limit} characters) exceeded, contains %{length} characters",
  368. limit: limit,
  369. length: length
  370. )}
  371. end
  372. end
  373. defp fix_user_message(
  374. %User{ap_id: actor} = user,
  375. %{"type" => "Delete", "object" => object} = activity
  376. ) do
  377. with {_, %Object{data: object_data}} <- {:normalize, Object.normalize(object, fetch: false)},
  378. {_, true} <- {:permission, user.is_moderator || actor == object_data["actor"]} do
  379. {:ok, activity}
  380. else
  381. {:normalize, _} ->
  382. {:error, "No such object found"}
  383. {:permission, _} ->
  384. {:forbidden, "You can't delete this object"}
  385. end
  386. end
  387. defp fix_user_message(%User{}, activity) do
  388. {:ok, activity}
  389. end
  390. def update_outbox(
  391. %{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn,
  392. %{"nickname" => nickname} = params
  393. ) do
  394. params =
  395. params
  396. |> Map.drop(["nickname"])
  397. |> Map.put("id", Utils.generate_activity_id())
  398. |> Map.put("actor", actor)
  399. |> Map.put("published", Utils.make_date())
  400. with {:ok, params} <- fix_user_message(user, params),
  401. {:ok, activity, _} <- Pipeline.common_pipeline(params, local: true),
  402. %Activity{data: activity_data} <- Activity.normalize(activity) do
  403. conn
  404. |> put_status(:created)
  405. |> put_resp_header("location", activity_data["id"])
  406. |> json(activity_data)
  407. else
  408. {:forbidden, message} ->
  409. conn
  410. |> put_status(:forbidden)
  411. |> json(message)
  412. {:error, message} ->
  413. conn
  414. |> put_status(:bad_request)
  415. |> json(message)
  416. e ->
  417. Logger.warning(fn -> "AP C2S: #{inspect(e)}" end)
  418. conn
  419. |> put_status(:bad_request)
  420. |> json("Bad Request")
  421. end
  422. end
  423. def update_outbox(%{assigns: %{user: %User{} = user}} = conn, %{"nickname" => nickname}) do
  424. err =
  425. dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
  426. nickname: nickname,
  427. as_nickname: user.nickname
  428. )
  429. conn
  430. |> put_status(:forbidden)
  431. |> json(err)
  432. end
  433. defp errors(conn, {:error, :not_found}) do
  434. conn
  435. |> put_status(:not_found)
  436. |> json(dgettext("errors", "Not found"))
  437. end
  438. defp errors(conn, _e) do
  439. conn
  440. |> put_status(:internal_server_error)
  441. |> json(dgettext("errors", "error"))
  442. end
  443. defp set_requester_reachable(%Plug.Conn{} = conn, _) do
  444. with actor <- conn.params["actor"],
  445. true <- is_binary(actor) do
  446. Pleroma.Instances.set_reachable(actor)
  447. end
  448. conn
  449. end
  450. def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
  451. with {:ok, object} <-
  452. ActivityPub.upload(
  453. file,
  454. actor: User.ap_id(user),
  455. description: Map.get(data, "description")
  456. ) do
  457. Logger.debug(inspect(object))
  458. conn
  459. |> put_status(:created)
  460. |> json(object.data)
  461. end
  462. end
  463. def pinned(conn, %{"nickname" => nickname}) do
  464. with %User{} = user <- User.get_cached_by_nickname(nickname) do
  465. conn
  466. |> put_resp_header("content-type", "application/activity+json")
  467. |> json(UserView.render("featured.json", %{user: user}))
  468. end
  469. end
  470. end