1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do
6 use Pleroma.Web.ConnCase
10 alias Pleroma.UnstubbedConfigMock, as: ConfigMock
12 alias Pleroma.Web.CommonAPI
15 import ExUnit.CaptureLog
17 import Pleroma.Factory
19 setup_all do: clear_config([:instance, :federating], true)
20 setup do: clear_config([:user, :deny_follow_blocked])
22 describe "GET /ostatus_subscribe - remote_follow/2" do
23 test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do
25 %{method: :get, url: "https://mastodon.social/users/emelie/statuses/101849165031453009"} ->
28 headers: [{"content-type", "application/activity+json"}],
29 body: File.read!("test/fixtures/tesla_mock/status.emelie.json")
32 %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
35 headers: [{"content-type", "application/activity+json"}],
37 File.read!("test/fixtures/users_mock/masto_featured.json")
38 |> String.replace("{{domain}}", "mastodon.social")
39 |> String.replace("{{nickname}}", "emelie")
42 %{method: :get, url: "https://mastodon.social/users/emelie"} ->
45 headers: [{"content-type", "application/activity+json"}],
46 body: File.read!("test/fixtures/tesla_mock/emelie.json")
52 remote_follow_path(conn, :follow, %{
53 acct: "https://mastodon.social/users/emelie/statuses/101849165031453009"
56 |> redirected_to() =~ "/notice/"
59 test "show follow account page if the `acct` is a account link", %{conn: conn} do
61 %{method: :get, url: "https://mastodon.social/users/emelie"} ->
64 headers: [{"content-type", "application/activity+json"}],
65 body: File.read!("test/fixtures/tesla_mock/emelie.json")
68 %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
71 headers: [{"content-type", "application/activity+json"}],
73 File.read!("test/fixtures/users_mock/masto_featured.json")
74 |> String.replace("{{domain}}", "mastodon.social")
75 |> String.replace("{{nickname}}", "emelie")
81 |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"}))
84 assert response =~ "Log in to follow"
87 test "show follow page if the `acct` is a account link", %{conn: conn} do
89 %{method: :get, url: "https://mastodon.social/users/emelie"} ->
92 headers: [{"content-type", "application/activity+json"}],
93 body: File.read!("test/fixtures/tesla_mock/emelie.json")
96 %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
99 headers: [{"content-type", "application/activity+json"}],
101 File.read!("test/fixtures/users_mock/masto_featured.json")
102 |> String.replace("{{domain}}", "mastodon.social")
103 |> String.replace("{{nickname}}", "emelie")
111 |> assign(:user, user)
112 |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"}))
113 |> html_response(200)
115 assert response =~ "Remote follow"
118 test "show follow page with error when user can not be fetched by `acct` link", %{conn: conn} do
120 %{method: :get, url: "https://mastodon.social/users/not_found"} ->
128 assert capture_log(fn ->
131 |> assign(:user, user)
133 remote_follow_path(conn, :follow, %{
134 acct: "https://mastodon.social/users/not_found"
137 |> html_response(200)
139 assert response =~ "Error fetching user"
144 describe "POST /ostatus_subscribe - do_follow/2 with assigned user " do
145 test "required `follow | write:follows` scope", %{conn: conn} do
147 user2 = insert(:user)
148 read_token = insert(:oauth_token, user: user, scopes: ["read"])
150 assert capture_log(fn ->
153 |> assign(:user, user)
154 |> assign(:token, read_token)
155 |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
158 assert response =~ "Error following account"
159 end) =~ "Insufficient permissions: follow | write:follows."
162 test "follows user", %{conn: conn} do
164 user2 = insert(:user)
168 |> assign(:user, user)
169 |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"]))
170 |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
172 assert redirected_to(conn) == "/users/#{user2.id}"
175 test "returns error when user is deactivated", %{conn: conn} do
176 user = insert(:user, is_active: false)
177 user2 = insert(:user)
181 |> assign(:user, user)
182 |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
185 assert response =~ "Error following account"
188 test "returns error when user is blocked", %{conn: conn} do
189 clear_config([:user, :deny_follow_blocked], true)
191 user2 = insert(:user)
193 {:ok, _user_block} = Pleroma.User.block(user2, user)
197 |> assign(:user, user)
198 |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
201 assert response =~ "Error following account"
204 test "returns error when followee not found", %{conn: conn} do
209 |> assign(:user, user)
210 |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => "jimm"}})
213 assert response =~ "Error following account"
216 test "returns success result when user already in followers", %{conn: conn} do
218 user2 = insert(:user)
219 {:ok, _, _, _} = CommonAPI.follow(user, user2)
223 |> assign(:user, refresh_record(user))
224 |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"]))
225 |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
227 assert redirected_to(conn) == "/users/#{user2.id}"
231 describe "POST /ostatus_subscribe - follow/2 with enabled Two-Factor Auth " do
232 test "render the MFA login form", %{conn: conn} do
233 otp_secret = TOTP.generate_secret()
237 multi_factor_authentication_settings: %MFA.Settings{
239 totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
243 user2 = insert(:user)
247 |> post(remote_follow_path(conn, :do_follow), %{
248 "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
252 mfa_token = Pleroma.Repo.one(from(q in Pleroma.MFA.Token, where: q.user_id == ^user.id))
254 assert response =~ "Two-factor authentication"
255 assert response =~ "Authentication code"
256 assert response =~ mfa_token.token
257 refute user2.follower_address in User.following(user)
260 test "returns error when password is incorrect", %{conn: conn} do
261 otp_secret = TOTP.generate_secret()
265 multi_factor_authentication_settings: %MFA.Settings{
267 totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
271 user2 = insert(:user)
275 |> post(remote_follow_path(conn, :do_follow), %{
276 "authorization" => %{"name" => user.nickname, "password" => "test1", "id" => user2.id}
280 assert response =~ "Wrong username or password"
281 refute user2.follower_address in User.following(user)
284 test "follows", %{conn: conn} do
285 otp_secret = TOTP.generate_secret()
289 multi_factor_authentication_settings: %MFA.Settings{
291 totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
295 {:ok, %{token: token}} = MFA.Token.create(user)
297 user2 = insert(:user)
298 otp_token = TOTP.generate_token(otp_secret)
303 remote_follow_path(conn, :do_follow),
305 "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id}
309 assert redirected_to(conn) == "/users/#{user2.id}"
310 assert user2.follower_address in User.following(user)
313 test "returns error when auth code is incorrect", %{conn: conn} do
314 otp_secret = TOTP.generate_secret()
318 multi_factor_authentication_settings: %MFA.Settings{
320 totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
324 {:ok, %{token: token}} = MFA.Token.create(user)
326 user2 = insert(:user)
327 otp_token = TOTP.generate_token(TOTP.generate_secret())
332 remote_follow_path(conn, :do_follow),
334 "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id}
339 assert response =~ "Wrong authentication code"
340 refute user2.follower_address in User.following(user)
344 describe "POST /ostatus_subscribe - follow/2 without assigned user " do
345 test "follows", %{conn: conn} do
347 user2 = insert(:user)
351 |> post(remote_follow_path(conn, :do_follow), %{
352 "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
355 assert redirected_to(conn) == "/users/#{user2.id}"
356 assert user2.follower_address in User.following(user)
359 test "returns error when followee not found", %{conn: conn} do
364 |> post(remote_follow_path(conn, :do_follow), %{
365 "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"}
369 assert response =~ "Error following account"
372 test "returns error when login invalid", %{conn: conn} do
377 |> post(remote_follow_path(conn, :do_follow), %{
378 "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id}
382 assert response =~ "Wrong username or password"
385 test "returns error when password invalid", %{conn: conn} do
387 user2 = insert(:user)
391 |> post(remote_follow_path(conn, :do_follow), %{
392 "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id}
396 assert response =~ "Wrong username or password"
399 test "returns error when user is blocked", %{conn: conn} do
400 clear_config([:user, :deny_follow_blocked], true)
402 user2 = insert(:user)
403 {:ok, _user_block} = Pleroma.User.block(user2, user)
407 |> post(remote_follow_path(conn, :do_follow), %{
408 "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
412 assert response =~ "Error following account"
416 describe "avatar url" do
417 test "without media proxy" do
418 clear_config([:media_proxy, :enabled], false)
423 avatar: %{"url" => [%{"href" => "https://remote.org/avatar.png"}]}
426 avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user)
428 assert avatar_url == "https://remote.org/avatar.png"
431 test "with media proxy" do
432 clear_config([:media_proxy, :enabled], true)
435 |> stub_with(Pleroma.Test.StaticConfig)
440 avatar: %{"url" => [%{"href" => "https://remote.org/avatar.png"}]}
443 avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user)
444 url = Pleroma.Web.Endpoint.url()
446 assert String.starts_with?(avatar_url, url)
449 test "local avatar is not proxied" do
450 clear_config([:media_proxy, :enabled], true)
455 avatar: %{"url" => [%{"href" => "#{Pleroma.Web.Endpoint.url()}/localuser/avatar.png"}]}
458 avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user)
460 assert avatar_url == "#{Pleroma.Web.Endpoint.url()}/localuser/avatar.png"
464 describe "GET /authorize_interaction - authorize_interaction/2" do
465 test "redirects to /ostatus_subscribe", %{conn: conn} do
467 %{method: :get, url: "https://mastodon.social/users/emelie"} ->
470 headers: [{"content-type", "application/activity+json"}],
471 body: File.read!("test/fixtures/tesla_mock/emelie.json")
474 %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
477 headers: [{"content-type", "application/activity+json"}],
479 File.read!("test/fixtures/users_mock/masto_featured.json")
480 |> String.replace("{{domain}}", "mastodon.social")
481 |> String.replace("{{nickname}}", "emelie")
488 remote_follow_path(conn, :authorize_interaction, %{
489 uri: "https://mastodon.social/users/emelie"
493 assert redirected_to(conn) ==
494 remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})