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