Merge branch 'feature/1206-restrict-unauthenticated' into 'develop'
[akkoma] / test / web / mastodon_api / controllers / timeline_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
6 use Pleroma.Web.ConnCase
7
8 import Pleroma.Factory
9 import Tesla.Mock
10
11 alias Pleroma.Config
12 alias Pleroma.User
13 alias Pleroma.Web.CommonAPI
14
15 setup do
16 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
17 :ok
18 end
19
20 describe "home" do
21 setup do: oauth_access(["read:statuses"])
22
23 test "the home timeline", %{user: user, conn: conn} do
24 following = insert(:user)
25
26 {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
27
28 ret_conn = get(conn, "/api/v1/timelines/home")
29
30 assert Enum.empty?(json_response(ret_conn, :ok))
31
32 {:ok, _user} = User.follow(user, following)
33
34 conn = get(conn, "/api/v1/timelines/home")
35
36 assert [%{"content" => "test"}] = json_response(conn, :ok)
37 end
38
39 test "the home timeline when the direct messages are excluded", %{user: user, conn: conn} do
40 {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
41 {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
42
43 {:ok, unlisted_activity} =
44 CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
45
46 {:ok, private_activity} =
47 CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
48
49 conn = get(conn, "/api/v1/timelines/home", %{"exclude_visibilities" => ["direct"]})
50
51 assert status_ids = json_response(conn, :ok) |> Enum.map(& &1["id"])
52 assert public_activity.id in status_ids
53 assert unlisted_activity.id in status_ids
54 assert private_activity.id in status_ids
55 refute direct_activity.id in status_ids
56 end
57 end
58
59 describe "public" do
60 @tag capture_log: true
61 test "the public timeline", %{conn: conn} do
62 following = insert(:user)
63
64 {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
65
66 _activity = insert(:note_activity, local: false)
67
68 conn = get(conn, "/api/v1/timelines/public", %{"local" => "False"})
69
70 assert length(json_response(conn, :ok)) == 2
71
72 conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "True"})
73
74 assert [%{"content" => "test"}] = json_response(conn, :ok)
75
76 conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "1"})
77
78 assert [%{"content" => "test"}] = json_response(conn, :ok)
79 end
80
81 test "the public timeline includes only public statuses for an authenticated user" do
82 %{user: user, conn: conn} = oauth_access(["read:statuses"])
83
84 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test"})
85 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "private"})
86 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "unlisted"})
87 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
88
89 res_conn = get(conn, "/api/v1/timelines/public")
90 assert length(json_response(res_conn, 200)) == 1
91 end
92 end
93
94 defp local_and_remote_activities do
95 insert(:note_activity)
96 insert(:note_activity, local: false)
97 :ok
98 end
99
100 describe "public with restrict unauthenticated timeline for local and federated timelines" do
101 setup do: local_and_remote_activities()
102
103 clear_config([:restrict_unauthenticated, :timelines, :local]) do
104 Config.put([:restrict_unauthenticated, :timelines, :local], true)
105 end
106
107 clear_config([:restrict_unauthenticated, :timelines, :federated]) do
108 Config.put([:restrict_unauthenticated, :timelines, :federated], true)
109 end
110
111 test "if user is unauthenticated", %{conn: conn} do
112 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
113
114 assert json_response(res_conn, :unauthorized) == %{
115 "error" => "authorization required for timeline view"
116 }
117
118 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
119
120 assert json_response(res_conn, :unauthorized) == %{
121 "error" => "authorization required for timeline view"
122 }
123 end
124
125 test "if user is authenticated" do
126 %{conn: conn} = oauth_access(["read:statuses"])
127
128 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
129 assert length(json_response(res_conn, 200)) == 1
130
131 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
132 assert length(json_response(res_conn, 200)) == 2
133 end
134 end
135
136 describe "public with restrict unauthenticated timeline for local" do
137 setup do: local_and_remote_activities()
138
139 clear_config([:restrict_unauthenticated, :timelines, :local]) do
140 Config.put([:restrict_unauthenticated, :timelines, :local], true)
141 end
142
143 test "if user is unauthenticated", %{conn: conn} do
144 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
145
146 assert json_response(res_conn, :unauthorized) == %{
147 "error" => "authorization required for timeline view"
148 }
149
150 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
151 assert length(json_response(res_conn, 200)) == 2
152 end
153
154 test "if user is authenticated", %{conn: _conn} do
155 %{conn: conn} = oauth_access(["read:statuses"])
156
157 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
158 assert length(json_response(res_conn, 200)) == 1
159
160 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
161 assert length(json_response(res_conn, 200)) == 2
162 end
163 end
164
165 describe "public with restrict unauthenticated timeline for remote" do
166 setup do: local_and_remote_activities()
167
168 clear_config([:restrict_unauthenticated, :timelines, :federated]) do
169 Config.put([:restrict_unauthenticated, :timelines, :federated], true)
170 end
171
172 test "if user is unauthenticated", %{conn: conn} do
173 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
174 assert length(json_response(res_conn, 200)) == 1
175
176 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
177
178 assert json_response(res_conn, :unauthorized) == %{
179 "error" => "authorization required for timeline view"
180 }
181 end
182
183 test "if user is authenticated", %{conn: _conn} do
184 %{conn: conn} = oauth_access(["read:statuses"])
185
186 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
187 assert length(json_response(res_conn, 200)) == 1
188
189 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
190 assert length(json_response(res_conn, 200)) == 2
191 end
192 end
193
194 describe "direct" do
195 test "direct timeline", %{conn: conn} do
196 user_one = insert(:user)
197 user_two = insert(:user)
198
199 {:ok, user_two} = User.follow(user_two, user_one)
200
201 {:ok, direct} =
202 CommonAPI.post(user_one, %{
203 "status" => "Hi @#{user_two.nickname}!",
204 "visibility" => "direct"
205 })
206
207 {:ok, _follower_only} =
208 CommonAPI.post(user_one, %{
209 "status" => "Hi @#{user_two.nickname}!",
210 "visibility" => "private"
211 })
212
213 conn_user_two =
214 conn
215 |> assign(:user, user_two)
216 |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"]))
217
218 # Only direct should be visible here
219 res_conn = get(conn_user_two, "api/v1/timelines/direct")
220
221 [status] = json_response(res_conn, :ok)
222
223 assert %{"visibility" => "direct"} = status
224 assert status["url"] != direct.data["id"]
225
226 # User should be able to see their own direct message
227 res_conn =
228 build_conn()
229 |> assign(:user, user_one)
230 |> assign(:token, insert(:oauth_token, user: user_one, scopes: ["read:statuses"]))
231 |> get("api/v1/timelines/direct")
232
233 [status] = json_response(res_conn, :ok)
234
235 assert %{"visibility" => "direct"} = status
236
237 # Both should be visible here
238 res_conn = get(conn_user_two, "api/v1/timelines/home")
239
240 [_s1, _s2] = json_response(res_conn, :ok)
241
242 # Test pagination
243 Enum.each(1..20, fn _ ->
244 {:ok, _} =
245 CommonAPI.post(user_one, %{
246 "status" => "Hi @#{user_two.nickname}!",
247 "visibility" => "direct"
248 })
249 end)
250
251 res_conn = get(conn_user_two, "api/v1/timelines/direct")
252
253 statuses = json_response(res_conn, :ok)
254 assert length(statuses) == 20
255
256 res_conn =
257 get(conn_user_two, "api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
258
259 [status] = json_response(res_conn, :ok)
260
261 assert status["url"] != direct.data["id"]
262 end
263
264 test "doesn't include DMs from blocked users" do
265 %{user: blocker, conn: conn} = oauth_access(["read:statuses"])
266 blocked = insert(:user)
267 other_user = insert(:user)
268 {:ok, _user_relationship} = User.block(blocker, blocked)
269
270 {:ok, _blocked_direct} =
271 CommonAPI.post(blocked, %{
272 "status" => "Hi @#{blocker.nickname}!",
273 "visibility" => "direct"
274 })
275
276 {:ok, direct} =
277 CommonAPI.post(other_user, %{
278 "status" => "Hi @#{blocker.nickname}!",
279 "visibility" => "direct"
280 })
281
282 res_conn = get(conn, "api/v1/timelines/direct")
283
284 [status] = json_response(res_conn, :ok)
285 assert status["id"] == direct.id
286 end
287 end
288
289 describe "list" do
290 setup do: oauth_access(["read:lists"])
291
292 test "list timeline", %{user: user, conn: conn} do
293 other_user = insert(:user)
294 {:ok, _activity_one} = CommonAPI.post(user, %{"status" => "Marisa is cute."})
295 {:ok, activity_two} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
296 {:ok, list} = Pleroma.List.create("name", user)
297 {:ok, list} = Pleroma.List.follow(list, other_user)
298
299 conn = get(conn, "/api/v1/timelines/list/#{list.id}")
300
301 assert [%{"id" => id}] = json_response(conn, :ok)
302
303 assert id == to_string(activity_two.id)
304 end
305
306 test "list timeline does not leak non-public statuses for unfollowed users", %{
307 user: user,
308 conn: conn
309 } do
310 other_user = insert(:user)
311 {:ok, activity_one} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
312
313 {:ok, _activity_two} =
314 CommonAPI.post(other_user, %{
315 "status" => "Marisa is cute.",
316 "visibility" => "private"
317 })
318
319 {:ok, list} = Pleroma.List.create("name", user)
320 {:ok, list} = Pleroma.List.follow(list, other_user)
321
322 conn = get(conn, "/api/v1/timelines/list/#{list.id}")
323
324 assert [%{"id" => id}] = json_response(conn, :ok)
325
326 assert id == to_string(activity_one.id)
327 end
328 end
329
330 describe "hashtag" do
331 setup do: oauth_access(["n/a"])
332
333 @tag capture_log: true
334 test "hashtag timeline", %{conn: conn} do
335 following = insert(:user)
336
337 {:ok, activity} = CommonAPI.post(following, %{"status" => "test #2hu"})
338
339 nconn = get(conn, "/api/v1/timelines/tag/2hu")
340
341 assert [%{"id" => id}] = json_response(nconn, :ok)
342
343 assert id == to_string(activity.id)
344
345 # works for different capitalization too
346 nconn = get(conn, "/api/v1/timelines/tag/2HU")
347
348 assert [%{"id" => id}] = json_response(nconn, :ok)
349
350 assert id == to_string(activity.id)
351 end
352
353 test "multi-hashtag timeline", %{conn: conn} do
354 user = insert(:user)
355
356 {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
357 {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
358 {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
359
360 any_test = get(conn, "/api/v1/timelines/tag/test", %{"any" => ["test1"]})
361
362 [status_none, status_test1, status_test] = json_response(any_test, :ok)
363
364 assert to_string(activity_test.id) == status_test["id"]
365 assert to_string(activity_test1.id) == status_test1["id"]
366 assert to_string(activity_none.id) == status_none["id"]
367
368 restricted_test =
369 get(conn, "/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
370
371 assert [status_test1] == json_response(restricted_test, :ok)
372
373 all_test = get(conn, "/api/v1/timelines/tag/test", %{"all" => ["none"]})
374
375 assert [status_none] == json_response(all_test, :ok)
376 end
377 end
378 end