Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

555 行
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.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(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:upload_media])
  40. plug(
  41. Pleroma.Web.Plugs.Cache,
  42. [query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
  43. when action in [:activity, :object]
  44. )
  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),
  58. {:ok, user} <- User.ensure_keys_present(user) do
  59. conn
  60. |> put_resp_content_type("application/activity+json")
  61. |> put_view(UserView)
  62. |> render("user.json", %{user: user})
  63. else
  64. nil -> {:error, :not_found}
  65. %{local: false} -> {:error, :not_found}
  66. end
  67. end
  68. def object(%{assigns: assigns} = conn, _) do
  69. with ap_id <- Endpoint.url() <> conn.request_path,
  70. %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
  71. user <- Map.get(assigns, :user, nil),
  72. {_, true} <- {:visible?, Visibility.visible_for_user?(object, user)} do
  73. conn
  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_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. # GET /relay/following
  130. def relay_following(conn, _params) do
  131. with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
  132. conn
  133. |> put_resp_content_type("application/activity+json")
  134. |> put_view(UserView)
  135. |> render("following.json", %{user: Relay.get_actor()})
  136. end
  137. end
  138. def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
  139. with %User{} = user <- User.get_cached_by_nickname(nickname),
  140. {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
  141. {:show_follows, true} <-
  142. {:show_follows, (for_user && for_user == user) || !user.hide_follows} do
  143. {page, _} = Integer.parse(page)
  144. conn
  145. |> put_resp_content_type("application/activity+json")
  146. |> put_view(UserView)
  147. |> render("following.json", %{user: user, page: page, for: for_user})
  148. else
  149. {:show_follows, _} ->
  150. conn
  151. |> put_resp_content_type("application/activity+json")
  152. |> send_resp(403, "")
  153. end
  154. end
  155. def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
  156. with %User{} = user <- User.get_cached_by_nickname(nickname),
  157. {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
  158. conn
  159. |> put_resp_content_type("application/activity+json")
  160. |> put_view(UserView)
  161. |> render("following.json", %{user: user, for: for_user})
  162. end
  163. end
  164. # GET /relay/followers
  165. def relay_followers(conn, _params) do
  166. with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
  167. conn
  168. |> put_resp_content_type("application/activity+json")
  169. |> put_view(UserView)
  170. |> render("followers.json", %{user: Relay.get_actor()})
  171. end
  172. end
  173. def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
  174. with %User{} = user <- User.get_cached_by_nickname(nickname),
  175. {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
  176. {:show_followers, true} <-
  177. {:show_followers, (for_user && for_user == user) || !user.hide_followers} do
  178. {page, _} = Integer.parse(page)
  179. conn
  180. |> put_resp_content_type("application/activity+json")
  181. |> put_view(UserView)
  182. |> render("followers.json", %{user: user, page: page, for: for_user})
  183. else
  184. {:show_followers, _} ->
  185. conn
  186. |> put_resp_content_type("application/activity+json")
  187. |> send_resp(403, "")
  188. end
  189. end
  190. def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
  191. with %User{} = user <- User.get_cached_by_nickname(nickname),
  192. {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
  193. conn
  194. |> put_resp_content_type("application/activity+json")
  195. |> put_view(UserView)
  196. |> render("followers.json", %{user: user, for: for_user})
  197. end
  198. end
  199. def outbox(
  200. %{assigns: %{user: for_user}} = conn,
  201. %{"nickname" => nickname, "page" => page?} = params
  202. )
  203. when page? in [true, "true"] do
  204. with %User{} = user <- User.get_cached_by_nickname(nickname),
  205. {:ok, user} <- User.ensure_keys_present(user) do
  206. # "include_poll_votes" is a hack because postgres generates inefficient
  207. # queries when filtering by 'Answer', poll votes will be hidden by the
  208. # visibility filter in this case anyway
  209. params =
  210. params
  211. |> Map.drop(["nickname", "page"])
  212. |> Map.put("include_poll_votes", true)
  213. |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
  214. activities = ActivityPub.fetch_user_activities(user, for_user, params)
  215. conn
  216. |> put_resp_content_type("application/activity+json")
  217. |> put_view(UserView)
  218. |> render("activity_collection_page.json", %{
  219. activities: activities,
  220. pagination: ControllerHelper.get_pagination_fields(conn, activities),
  221. iri: "#{user.ap_id}/outbox"
  222. })
  223. end
  224. end
  225. def outbox(conn, %{"nickname" => nickname}) do
  226. with %User{} = user <- User.get_cached_by_nickname(nickname),
  227. {:ok, user} <- User.ensure_keys_present(user) do
  228. conn
  229. |> put_resp_content_type("application/activity+json")
  230. |> put_view(UserView)
  231. |> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"})
  232. end
  233. end
  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)
  240. json(conn, "ok")
  241. end
  242. end
  243. def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
  244. Federator.incoming_ap_doc(params)
  245. json(conn, "ok")
  246. end
  247. # POST /relay/inbox -or- POST /internal/fetch/inbox
  248. def inbox(conn, params) do
  249. if params["type"] == "Create" && FederatingPlug.federating?() do
  250. post_inbox_relayed_create(conn, params)
  251. else
  252. post_inbox_fallback(conn, params)
  253. end
  254. end
  255. defp post_inbox_relayed_create(conn, params) do
  256. Logger.debug(
  257. "Signature missing or not from author, relayed Create message, fetching object from source"
  258. )
  259. Fetcher.fetch_object_from_id(params["object"]["id"])
  260. json(conn, "ok")
  261. end
  262. defp post_inbox_fallback(conn, params) do
  263. headers = Enum.into(conn.req_headers, %{})
  264. if headers["signature"] && params["actor"] &&
  265. String.contains?(headers["signature"], params["actor"]) do
  266. Logger.debug(
  267. "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
  268. )
  269. Logger.debug(inspect(conn.req_headers))
  270. end
  271. conn
  272. |> put_status(:bad_request)
  273. |> json(dgettext("errors", "error"))
  274. end
  275. defp represent_service_actor(%User{} = user, conn) do
  276. with {:ok, user} <- User.ensure_keys_present(user) do
  277. conn
  278. |> put_resp_content_type("application/activity+json")
  279. |> put_view(UserView)
  280. |> render("user.json", %{user: user})
  281. else
  282. nil -> {:error, :not_found}
  283. end
  284. end
  285. defp represent_service_actor(nil, _), do: {:error, :not_found}
  286. def relay(conn, _params) do
  287. Relay.get_actor()
  288. |> represent_service_actor(conn)
  289. end
  290. def internal_fetch(conn, _params) do
  291. InternalFetchActor.get_actor()
  292. |> represent_service_actor(conn)
  293. end
  294. @doc "Returns the authenticated user's ActivityPub User object or a 404 Not Found if non-authenticated"
  295. def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
  296. conn
  297. |> put_resp_content_type("application/activity+json")
  298. |> put_view(UserView)
  299. |> render("user.json", %{user: user})
  300. end
  301. def read_inbox(
  302. %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
  303. %{"nickname" => nickname, "page" => page?} = params
  304. )
  305. when page? in [true, "true"] do
  306. params =
  307. params
  308. |> Map.drop(["nickname", "page"])
  309. |> Map.put("blocking_user", user)
  310. |> Map.put("user", user)
  311. |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
  312. activities =
  313. [user.ap_id | User.following(user)]
  314. |> ActivityPub.fetch_activities(params)
  315. |> Enum.reverse()
  316. conn
  317. |> put_resp_content_type("application/activity+json")
  318. |> put_view(UserView)
  319. |> render("activity_collection_page.json", %{
  320. activities: activities,
  321. pagination: ControllerHelper.get_pagination_fields(conn, activities),
  322. iri: "#{user.ap_id}/inbox"
  323. })
  324. end
  325. def read_inbox(%{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{
  326. "nickname" => nickname
  327. }) do
  328. with {:ok, user} <- User.ensure_keys_present(user) 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. end
  335. def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{
  336. "nickname" => nickname
  337. }) do
  338. err =
  339. dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
  340. nickname: nickname,
  341. as_nickname: as_nickname
  342. )
  343. conn
  344. |> put_status(:forbidden)
  345. |> json(err)
  346. end
  347. defp handle_user_activity(
  348. %User{} = user,
  349. %{"type" => "Create", "object" => %{"type" => "Note"} = object} = params
  350. ) do
  351. content = if is_binary(object["content"]), do: object["content"], else: ""
  352. name = if is_binary(object["name"]), do: object["name"], else: ""
  353. summary = if is_binary(object["summary"]), do: object["summary"], else: ""
  354. length = String.length(content <> name <> summary)
  355. if length > Pleroma.Config.get([:instance, :limit]) do
  356. {:error, dgettext("errors", "Note is over the character limit")}
  357. else
  358. object =
  359. object
  360. |> Map.merge(Map.take(params, ["to", "cc"]))
  361. |> Map.put("attributedTo", user.ap_id)
  362. |> Transmogrifier.fix_object()
  363. ActivityPub.create(%{
  364. to: params["to"],
  365. actor: user,
  366. context: object["context"],
  367. object: object,
  368. additional: Map.take(params, ["cc"])
  369. })
  370. end
  371. end
  372. defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do
  373. with %Object{} = object <- Object.normalize(params["object"], fetch: false),
  374. true <- user.is_moderator || user.ap_id == object.data["actor"],
  375. {:ok, delete_data, _} <- Builder.delete(user, object.data["id"]),
  376. {:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do
  377. {:ok, delete}
  378. else
  379. _ -> {:error, dgettext("errors", "Can't delete object")}
  380. end
  381. end
  382. defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do
  383. with %Object{} = object <- Object.normalize(params["object"], fetch: false),
  384. {_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)},
  385. {_, {:ok, %Activity{} = activity, _meta}} <-
  386. {:common_pipeline,
  387. Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do
  388. {:ok, activity}
  389. else
  390. _ -> {:error, dgettext("errors", "Can't like object")}
  391. end
  392. end
  393. defp handle_user_activity(_, _) do
  394. {:error, dgettext("errors", "Unhandled activity type")}
  395. end
  396. def update_outbox(
  397. %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
  398. %{"nickname" => nickname} = params
  399. ) do
  400. actor = user.ap_id
  401. params =
  402. params
  403. |> Map.drop(["id"])
  404. |> Map.put("actor", actor)
  405. |> Transmogrifier.fix_addressing()
  406. with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
  407. conn
  408. |> put_status(:created)
  409. |> put_resp_header("location", activity.data["id"])
  410. |> json(activity.data)
  411. else
  412. {:error, message} ->
  413. conn
  414. |> put_status(:bad_request)
  415. |> json(message)
  416. end
  417. end
  418. def update_outbox(%{assigns: %{user: %User{} = user}} = conn, %{"nickname" => nickname}) do
  419. err =
  420. dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
  421. nickname: nickname,
  422. as_nickname: user.nickname
  423. )
  424. conn
  425. |> put_status(:forbidden)
  426. |> json(err)
  427. end
  428. defp errors(conn, {:error, :not_found}) do
  429. conn
  430. |> put_status(:not_found)
  431. |> json(dgettext("errors", "Not found"))
  432. end
  433. defp errors(conn, _e) do
  434. conn
  435. |> put_status(:internal_server_error)
  436. |> json(dgettext("errors", "error"))
  437. end
  438. defp set_requester_reachable(%Plug.Conn{} = conn, _) do
  439. with actor <- conn.params["actor"],
  440. true <- is_binary(actor) do
  441. Pleroma.Instances.set_reachable(actor)
  442. end
  443. conn
  444. end
  445. defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
  446. {:ok, new_user} = User.ensure_keys_present(user)
  447. for_user =
  448. if new_user != user and match?(%User{}, for_user) do
  449. User.get_cached_by_nickname(for_user.nickname)
  450. else
  451. for_user
  452. end
  453. {new_user, for_user}
  454. end
  455. def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
  456. with {:ok, object} <-
  457. ActivityPub.upload(
  458. file,
  459. actor: User.ap_id(user),
  460. description: Map.get(data, "description")
  461. ) do
  462. Logger.debug(inspect(object))
  463. conn
  464. |> put_status(:created)
  465. |> json(object.data)
  466. end
  467. end
  468. def pinned(conn, %{"nickname" => nickname}) do
  469. with %User{} = user <- User.get_cached_by_nickname(nickname) do
  470. conn
  471. |> put_resp_header("content-type", "application/activity+json")
  472. |> json(UserView.render("featured.json", %{user: user}))
  473. end
  474. end
  475. end