logo

pleroma

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

activity_pub_controller.ex (17099B)


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