logo

pleroma

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

remote_follow_controller_test.exs (15703B)


  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.TwitterAPI.RemoteFollowControllerTest do
  5. use Pleroma.Web.ConnCase
  6. alias Pleroma.MFA
  7. alias Pleroma.MFA.TOTP
  8. alias Pleroma.UnstubbedConfigMock, as: ConfigMock
  9. alias Pleroma.User
  10. alias Pleroma.Web.CommonAPI
  11. import Ecto.Query
  12. import ExUnit.CaptureLog
  13. import Mox
  14. import Pleroma.Factory
  15. setup_all do: clear_config([:instance, :federating], true)
  16. setup do: clear_config([:user, :deny_follow_blocked])
  17. describe "GET /ostatus_subscribe - remote_follow/2" do
  18. test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do
  19. Tesla.Mock.mock(fn
  20. %{method: :get, url: "https://mastodon.social/users/emelie/statuses/101849165031453009"} ->
  21. %Tesla.Env{
  22. status: 200,
  23. headers: [{"content-type", "application/activity+json"}],
  24. body: File.read!("test/fixtures/tesla_mock/status.emelie.json")
  25. }
  26. %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
  27. %Tesla.Env{
  28. status: 200,
  29. headers: [{"content-type", "application/activity+json"}],
  30. body:
  31. File.read!("test/fixtures/users_mock/masto_featured.json")
  32. |> String.replace("{{domain}}", "mastodon.social")
  33. |> String.replace("{{nickname}}", "emelie")
  34. }
  35. %{method: :get, url: "https://mastodon.social/users/emelie"} ->
  36. %Tesla.Env{
  37. status: 200,
  38. headers: [{"content-type", "application/activity+json"}],
  39. body: File.read!("test/fixtures/tesla_mock/emelie.json")
  40. }
  41. end)
  42. assert conn
  43. |> get(
  44. remote_follow_path(conn, :follow, %{
  45. acct: "https://mastodon.social/users/emelie/statuses/101849165031453009"
  46. })
  47. )
  48. |> redirected_to() =~ "/notice/"
  49. end
  50. test "show follow account page if the `acct` is a account link", %{conn: conn} do
  51. Tesla.Mock.mock(fn
  52. %{method: :get, url: "https://mastodon.social/users/emelie"} ->
  53. %Tesla.Env{
  54. status: 200,
  55. headers: [{"content-type", "application/activity+json"}],
  56. body: File.read!("test/fixtures/tesla_mock/emelie.json")
  57. }
  58. %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
  59. %Tesla.Env{
  60. status: 200,
  61. headers: [{"content-type", "application/activity+json"}],
  62. body:
  63. File.read!("test/fixtures/users_mock/masto_featured.json")
  64. |> String.replace("{{domain}}", "mastodon.social")
  65. |> String.replace("{{nickname}}", "emelie")
  66. }
  67. end)
  68. response =
  69. conn
  70. |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"}))
  71. |> html_response(200)
  72. assert response =~ "Log in to follow"
  73. end
  74. test "show follow page if the `acct` is a account link", %{conn: conn} do
  75. Tesla.Mock.mock(fn
  76. %{method: :get, url: "https://mastodon.social/users/emelie"} ->
  77. %Tesla.Env{
  78. status: 200,
  79. headers: [{"content-type", "application/activity+json"}],
  80. body: File.read!("test/fixtures/tesla_mock/emelie.json")
  81. }
  82. %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
  83. %Tesla.Env{
  84. status: 200,
  85. headers: [{"content-type", "application/activity+json"}],
  86. body:
  87. File.read!("test/fixtures/users_mock/masto_featured.json")
  88. |> String.replace("{{domain}}", "mastodon.social")
  89. |> String.replace("{{nickname}}", "emelie")
  90. }
  91. end)
  92. user = insert(:user)
  93. response =
  94. conn
  95. |> assign(:user, user)
  96. |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"}))
  97. |> html_response(200)
  98. assert response =~ "Remote follow"
  99. end
  100. test "show follow page with error when user can not be fetched by `acct` link", %{conn: conn} do
  101. Tesla.Mock.mock(fn
  102. %{method: :get, url: "https://mastodon.social/users/not_found"} ->
  103. %Tesla.Env{
  104. status: 404
  105. }
  106. end)
  107. user = insert(:user)
  108. assert capture_log(fn ->
  109. response =
  110. conn
  111. |> assign(:user, user)
  112. |> get(
  113. remote_follow_path(conn, :follow, %{
  114. acct: "https://mastodon.social/users/not_found"
  115. })
  116. )
  117. |> html_response(200)
  118. assert response =~ "Error fetching user"
  119. end) =~ ":not_found"
  120. end
  121. end
  122. describe "POST /ostatus_subscribe - do_follow/2 with assigned user " do
  123. test "required `follow | write:follows` scope", %{conn: conn} do
  124. user = insert(:user)
  125. user2 = insert(:user)
  126. read_token = insert(:oauth_token, user: user, scopes: ["read"])
  127. assert capture_log(fn ->
  128. response =
  129. conn
  130. |> assign(:user, user)
  131. |> assign(:token, read_token)
  132. |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
  133. |> response(200)
  134. assert response =~ "Error following account"
  135. end) =~ "Insufficient permissions: follow | write:follows."
  136. end
  137. test "follows user", %{conn: conn} do
  138. user = insert(:user)
  139. user2 = insert(:user)
  140. conn =
  141. conn
  142. |> assign(:user, user)
  143. |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"]))
  144. |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
  145. assert redirected_to(conn) == "/users/#{user2.id}"
  146. end
  147. test "returns error when user is deactivated", %{conn: conn} do
  148. user = insert(:user, is_active: false)
  149. user2 = insert(:user)
  150. response =
  151. conn
  152. |> assign(:user, user)
  153. |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
  154. |> response(200)
  155. assert response =~ "Error following account"
  156. end
  157. test "returns error when user is blocked", %{conn: conn} do
  158. clear_config([:user, :deny_follow_blocked], true)
  159. user = insert(:user)
  160. user2 = insert(:user)
  161. {:ok, _user_block} = Pleroma.User.block(user2, user)
  162. response =
  163. conn
  164. |> assign(:user, user)
  165. |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
  166. |> response(200)
  167. assert response =~ "Error following account"
  168. end
  169. test "returns error when followee not found", %{conn: conn} do
  170. user = insert(:user)
  171. response =
  172. conn
  173. |> assign(:user, user)
  174. |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => "jimm"}})
  175. |> response(200)
  176. assert response =~ "Error following account"
  177. end
  178. test "returns success result when user already in followers", %{conn: conn} do
  179. user = insert(:user)
  180. user2 = insert(:user)
  181. {:ok, _, _, _} = CommonAPI.follow(user, user2)
  182. conn =
  183. conn
  184. |> assign(:user, refresh_record(user))
  185. |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"]))
  186. |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
  187. assert redirected_to(conn) == "/users/#{user2.id}"
  188. end
  189. end
  190. describe "POST /ostatus_subscribe - follow/2 with enabled Two-Factor Auth " do
  191. test "render the MFA login form", %{conn: conn} do
  192. otp_secret = TOTP.generate_secret()
  193. user =
  194. insert(:user,
  195. multi_factor_authentication_settings: %MFA.Settings{
  196. enabled: true,
  197. totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
  198. }
  199. )
  200. user2 = insert(:user)
  201. response =
  202. conn
  203. |> post(remote_follow_path(conn, :do_follow), %{
  204. "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
  205. })
  206. |> response(200)
  207. mfa_token = Pleroma.Repo.one(from(q in Pleroma.MFA.Token, where: q.user_id == ^user.id))
  208. assert response =~ "Two-factor authentication"
  209. assert response =~ "Authentication code"
  210. assert response =~ mfa_token.token
  211. refute user2.follower_address in User.following(user)
  212. end
  213. test "returns error when password is incorrect", %{conn: conn} do
  214. otp_secret = TOTP.generate_secret()
  215. user =
  216. insert(:user,
  217. multi_factor_authentication_settings: %MFA.Settings{
  218. enabled: true,
  219. totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
  220. }
  221. )
  222. user2 = insert(:user)
  223. response =
  224. conn
  225. |> post(remote_follow_path(conn, :do_follow), %{
  226. "authorization" => %{"name" => user.nickname, "password" => "test1", "id" => user2.id}
  227. })
  228. |> response(200)
  229. assert response =~ "Wrong username or password"
  230. refute user2.follower_address in User.following(user)
  231. end
  232. test "follows", %{conn: conn} do
  233. otp_secret = TOTP.generate_secret()
  234. user =
  235. insert(:user,
  236. multi_factor_authentication_settings: %MFA.Settings{
  237. enabled: true,
  238. totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
  239. }
  240. )
  241. {:ok, %{token: token}} = MFA.Token.create(user)
  242. user2 = insert(:user)
  243. otp_token = TOTP.generate_token(otp_secret)
  244. conn =
  245. conn
  246. |> post(
  247. remote_follow_path(conn, :do_follow),
  248. %{
  249. "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id}
  250. }
  251. )
  252. assert redirected_to(conn) == "/users/#{user2.id}"
  253. assert user2.follower_address in User.following(user)
  254. end
  255. test "returns error when auth code is incorrect", %{conn: conn} do
  256. otp_secret = TOTP.generate_secret()
  257. user =
  258. insert(:user,
  259. multi_factor_authentication_settings: %MFA.Settings{
  260. enabled: true,
  261. totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
  262. }
  263. )
  264. {:ok, %{token: token}} = MFA.Token.create(user)
  265. user2 = insert(:user)
  266. otp_token = TOTP.generate_token(TOTP.generate_secret())
  267. response =
  268. conn
  269. |> post(
  270. remote_follow_path(conn, :do_follow),
  271. %{
  272. "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id}
  273. }
  274. )
  275. |> response(200)
  276. assert response =~ "Wrong authentication code"
  277. refute user2.follower_address in User.following(user)
  278. end
  279. end
  280. describe "POST /ostatus_subscribe - follow/2 without assigned user " do
  281. test "follows", %{conn: conn} do
  282. user = insert(:user)
  283. user2 = insert(:user)
  284. conn =
  285. conn
  286. |> post(remote_follow_path(conn, :do_follow), %{
  287. "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
  288. })
  289. assert redirected_to(conn) == "/users/#{user2.id}"
  290. assert user2.follower_address in User.following(user)
  291. end
  292. test "returns error when followee not found", %{conn: conn} do
  293. user = insert(:user)
  294. response =
  295. conn
  296. |> post(remote_follow_path(conn, :do_follow), %{
  297. "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"}
  298. })
  299. |> response(200)
  300. assert response =~ "Error following account"
  301. end
  302. test "returns error when login invalid", %{conn: conn} do
  303. user = insert(:user)
  304. response =
  305. conn
  306. |> post(remote_follow_path(conn, :do_follow), %{
  307. "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id}
  308. })
  309. |> response(200)
  310. assert response =~ "Wrong username or password"
  311. end
  312. test "returns error when password invalid", %{conn: conn} do
  313. user = insert(:user)
  314. user2 = insert(:user)
  315. response =
  316. conn
  317. |> post(remote_follow_path(conn, :do_follow), %{
  318. "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id}
  319. })
  320. |> response(200)
  321. assert response =~ "Wrong username or password"
  322. end
  323. test "returns error when user is blocked", %{conn: conn} do
  324. clear_config([:user, :deny_follow_blocked], true)
  325. user = insert(:user)
  326. user2 = insert(:user)
  327. {:ok, _user_block} = Pleroma.User.block(user2, user)
  328. response =
  329. conn
  330. |> post(remote_follow_path(conn, :do_follow), %{
  331. "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
  332. })
  333. |> response(200)
  334. assert response =~ "Error following account"
  335. end
  336. end
  337. describe "avatar url" do
  338. test "without media proxy" do
  339. clear_config([:media_proxy, :enabled], false)
  340. user =
  341. insert(:user, %{
  342. local: false,
  343. avatar: %{"url" => [%{"href" => "https://remote.org/avatar.png"}]}
  344. })
  345. avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user)
  346. assert avatar_url == "https://remote.org/avatar.png"
  347. end
  348. test "with media proxy" do
  349. clear_config([:media_proxy, :enabled], true)
  350. ConfigMock
  351. |> stub_with(Pleroma.Test.StaticConfig)
  352. user =
  353. insert(:user, %{
  354. local: false,
  355. avatar: %{"url" => [%{"href" => "https://remote.org/avatar.png"}]}
  356. })
  357. avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user)
  358. url = Pleroma.Web.Endpoint.url()
  359. assert String.starts_with?(avatar_url, url)
  360. end
  361. test "local avatar is not proxied" do
  362. clear_config([:media_proxy, :enabled], true)
  363. user =
  364. insert(:user, %{
  365. local: true,
  366. avatar: %{"url" => [%{"href" => "#{Pleroma.Web.Endpoint.url()}/localuser/avatar.png"}]}
  367. })
  368. avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user)
  369. assert avatar_url == "#{Pleroma.Web.Endpoint.url()}/localuser/avatar.png"
  370. end
  371. end
  372. describe "GET /authorize_interaction - authorize_interaction/2" do
  373. test "redirects to /ostatus_subscribe", %{conn: conn} do
  374. Tesla.Mock.mock(fn
  375. %{method: :get, url: "https://mastodon.social/users/emelie"} ->
  376. %Tesla.Env{
  377. status: 200,
  378. headers: [{"content-type", "application/activity+json"}],
  379. body: File.read!("test/fixtures/tesla_mock/emelie.json")
  380. }
  381. %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
  382. %Tesla.Env{
  383. status: 200,
  384. headers: [{"content-type", "application/activity+json"}],
  385. body:
  386. File.read!("test/fixtures/users_mock/masto_featured.json")
  387. |> String.replace("{{domain}}", "mastodon.social")
  388. |> String.replace("{{nickname}}", "emelie")
  389. }
  390. end)
  391. conn =
  392. conn
  393. |> get(
  394. remote_follow_path(conn, :authorize_interaction, %{
  395. uri: "https://mastodon.social/users/emelie"
  396. })
  397. )
  398. assert redirected_to(conn) ==
  399. remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})
  400. end
  401. end
  402. end