Leon4gr45 commited on
Commit
c368d78
·
verified ·
1 Parent(s): b691d08

Delete test/plausible_web/plugs/authorize_public_api_test.exs with huggingface_hub

Browse files
test/plausible_web/plugs/authorize_public_api_test.exs DELETED
@@ -1,618 +0,0 @@
1
- defmodule PlausibleWeb.Plugs.AuthorizePublicAPITest do
2
- use PlausibleWeb.ConnCase, async: false
3
-
4
- import ExUnit.CaptureLog
5
-
6
- alias PlausibleWeb.Plugs.AuthorizePublicAPI
7
- alias Plausible.Repo
8
-
9
- setup %{conn: conn} do
10
- {:ok, conn: prepare_conn_for_auth(conn)}
11
- end
12
-
13
- test "halts with error when bearer token is missing", %{conn: conn} do
14
- conn =
15
- conn
16
- |> get("/")
17
- |> assign(:api_scope, "stats:read:*")
18
- |> AuthorizePublicAPI.call(nil)
19
-
20
- assert conn.halted
21
- assert json_response(conn, 401)["error"] =~ "Missing API key."
22
- end
23
-
24
- test "halts with error when bearer token is invalid against read-only Stats API", %{conn: conn} do
25
- conn =
26
- conn
27
- |> put_req_header("authorization", "Bearer invalid")
28
- |> get("/")
29
- |> assign(:api_scope, "stats:read:*")
30
- |> AuthorizePublicAPI.call(nil)
31
-
32
- assert conn.halted
33
- assert json_response(conn, 401)["error"] =~ "Invalid API key or site ID."
34
- end
35
-
36
- test "halts with error when bearer token is invalid", %{conn: conn} do
37
- conn =
38
- conn
39
- |> put_req_header("authorization", "Bearer invalid")
40
- |> get("/")
41
- |> assign(:api_scope, "sites:provision:*")
42
- |> AuthorizePublicAPI.call(nil)
43
-
44
- assert conn.halted
45
- assert json_response(conn, 401)["error"] =~ "Invalid API key."
46
- end
47
-
48
- for query_string <- ["?foo=bar", "?site_id", "?site_id="] do
49
- test "halts with error when requesting site context API without site_id parameter (query string: #{query_string})",
50
- %{conn: conn} do
51
- # `site_id` param is checked for nil or empty
52
- # for `api_context: :site` APIs before the key is verified,
53
- # therefore the key here doesn't matter
54
- key = "123"
55
-
56
- conn =
57
- conn
58
- |> put_req_header("authorization", "Bearer #{key}")
59
- |> get("/#{unquote(query_string)}")
60
- |> assign(:api_context, :site)
61
- |> assign(:api_scope, "sites:read:*")
62
- |> AuthorizePublicAPI.call(nil)
63
-
64
- assert conn.halted
65
- assert json_response(conn, 400)["error"] =~ "Missing site ID."
66
- end
67
- end
68
-
69
- @tag :ee_only
70
- test "401 error has priority over 402 error", %{conn: conn} do
71
- user = new_user()
72
- _site = new_site(owner: user)
73
- api_key = insert_api_key(:team_scope_api_key, user: user)
74
-
75
- another_owner =
76
- new_user() |> subscribe_to_enterprise_plan(paddle_plan_id: "123321", features: [])
77
-
78
- another_site = new_site(owner: another_owner)
79
-
80
- conn =
81
- conn
82
- |> authorize(api_key, site: another_site, api_context: :site, api_scope: "stats:read:*")
83
-
84
- assert conn.halted
85
-
86
- assert json_response(conn, 401)["error"] =~
87
- "Invalid API key or site ID. Please make sure you're using a valid API key with access to the site you've requested."
88
- end
89
-
90
- for key_type <- [:legacy_api_key, :team_scope_api_key] do
91
- describe "#{key_type} ::" do
92
- test "halts with error on missing site ID when request made for stats:read:* scope API, even without :site API context set",
93
- %{conn: conn} do
94
- api_key = insert_api_key(unquote(key_type), user: new_user())
95
-
96
- conn =
97
- conn
98
- |> authorize(api_key, api_scope: "stats:read:*")
99
-
100
- assert conn.halted
101
- assert json_response(conn, 400)["error"] =~ "Missing site ID."
102
- end
103
-
104
- @tag :ee_only
105
- test "halts with error when site's team lacks feature access", %{conn: conn} do
106
- user = new_user() |> subscribe_to_enterprise_plan(paddle_plan_id: "123321", features: [])
107
- site = new_site(owner: user)
108
- api_key = insert_api_key(unquote(key_type), user: user)
109
-
110
- conn =
111
- conn
112
- |> authorize(api_key, api_scope: "stats:read:*", site: site)
113
-
114
- assert conn.halted
115
-
116
- assert json_response(conn, 402)["error"] =~
117
- "The account that owns this API key does not have access"
118
- end
119
-
120
- on_ee do
121
- test "rejects access to a site that is a consolidated view (unless instructed otherwise)",
122
- %{
123
- conn: conn
124
- } do
125
- user = new_user()
126
- api_key = insert_api_key(unquote(key_type), user: user, scopes: ["sites:provision:*"])
127
- {:ok, team} = new_user() |> Plausible.Teams.get_or_create()
128
- new_site(team: team)
129
- new_site(team: team)
130
- consolidated_view = new_consolidated_view(team)
131
-
132
- conn =
133
- conn
134
- |> authorize(api_key, site: consolidated_view, api_scope: "sites:provision:*")
135
-
136
- assert conn.halted
137
-
138
- assert json_response(conn, 400)["error"] =~
139
- "This operation is unavailable for a consolidated view"
140
- end
141
- end
142
-
143
- @tag :ee_only
144
- test "halts with error when upgrade is required", %{conn: conn} do
145
- user = new_user() |> subscribe_to_enterprise_plan(paddle_plan_id: "123321", features: [])
146
- site = new_site(owner: user)
147
- api_key = insert_api_key(unquote(key_type), user: user)
148
-
149
- conn =
150
- conn |> authorize(api_key, site: site, api_scope: "stats:read:*")
151
-
152
- assert conn.halted
153
-
154
- assert json_response(conn, 402)["error"] =~
155
- "The account that owns this API key does not have access"
156
- end
157
-
158
- test "halts with error when site is locked", %{conn: conn} do
159
- user = new_user()
160
- site = new_site(owner: user)
161
- site.team |> Ecto.Changeset.change(locked: true) |> Repo.update!()
162
- api_key = insert_api_key(unquote(key_type), user: user)
163
-
164
- conn =
165
- conn
166
- |> authorize(api_key, site: site, api_scope: "stats:read:*")
167
-
168
- assert conn.halted
169
- assert json_response(conn, 402)["error"] =~ "This Plausible site is locked"
170
- end
171
-
172
- test "halts with error when site ID is invalid", %{conn: conn} do
173
- user = new_user(trial_expiry_date: Date.utc_today())
174
- _site = new_site(owner: user)
175
- api_key = insert_api_key(unquote(key_type), user: user)
176
-
177
- conn =
178
- conn
179
- |> authorize(api_key, site: %{domain: "invalid.domain"}, api_scope: "stats:read:*")
180
-
181
- assert conn.halted
182
- assert json_response(conn, 401)["error"] =~ "Invalid API key or site ID."
183
- end
184
-
185
- test "halts with error when API key owner does not have access to the requested site", %{
186
- conn: conn
187
- } do
188
- user = new_user(trial_expiry_date: Date.utc_today())
189
- site = new_site()
190
- api_key = insert_api_key(unquote(key_type), user: user)
191
-
192
- conn =
193
- conn
194
- |> authorize(api_key, site: site, api_scope: "stats:read:*")
195
-
196
- assert conn.halted
197
- assert json_response(conn, 401)["error"] =~ "Invalid API key or site ID."
198
- end
199
-
200
- test "halts with error when API lacks required scope", %{conn: conn} do
201
- user = new_user()
202
- api_key = insert_api_key(unquote(key_type), user: user)
203
-
204
- conn =
205
- conn |> authorize(api_key, api_scope: "sites:provision:*")
206
-
207
- assert conn.halted
208
- assert json_response(conn, 401)["error"] =~ "Invalid API key."
209
- end
210
-
211
- test "halts with error when over burst request limit" do
212
- user = new_user()
213
- api_key = insert_api_key(unquote(key_type), user: user, scopes: ["sites:read:*"])
214
-
215
- limit = Plausible.Auth.ApiKey.burst_request_limit()
216
-
217
- # The limiter uses a fixed time window, so a client straddling a window
218
- # boundary can legitimately get up to `2 * limit` requests through in
219
- # quick succession. Only the `2 * limit + 1`th request is guaranteed to
220
- # be throttled, so that's what the test asserts (rather than assuming
221
- # the `limit + 1`th trips, which flakes near a boundary).
222
- conns =
223
- for _ <- 1..(2 * limit + 1) do
224
- get_fresh_conn() |> authorize(api_key, api_scope: "sites:read:*")
225
- end
226
-
227
- {allowed, rest} = Enum.split(conns, limit)
228
-
229
- # The first `limit` requests always fit within a single window and pass.
230
- assert Enum.all?(allowed, &(not &1.halted))
231
- assert Enum.all?(allowed, &(&1.assigns.current_user.id == user.id))
232
-
233
- # At least one of the remaining requests exceeds the per-window limit.
234
- assert throttled = Enum.find(rest, & &1.halted)
235
-
236
- assert json_response(throttled, 429)["error"] ==
237
- "Too many API requests in a short period of time. The limit is 60 per 10 seconds. Please throttle your requests."
238
- end
239
-
240
- test "halts with error when over hourly request limit" do
241
- # the lower value is ignored in the test
242
- {legacy_hourly_limit, team_hourly_limit} =
243
- case unquote(key_type) do
244
- :legacy_api_key -> {100, 1}
245
- :team_scope_api_key -> {1, 100}
246
- end
247
-
248
- patch_env(Plausible.Auth.ApiKey,
249
- legacy_per_user_hourly_request_limit: legacy_hourly_limit,
250
- # relax burst request limit to check hourly request limit
251
- burst_request_limit: 1_000,
252
- burst_period_seconds: 10
253
- )
254
-
255
- user = new_user(team: [hourly_api_request_limit: team_hourly_limit])
256
- api_key = insert_api_key(unquote(key_type), user: user)
257
-
258
- limit = 100
259
-
260
- # See the burst test above: with a fixed window, only the
261
- # `2 * limit + 1`th request is guaranteed to be throttled.
262
- conns =
263
- for _ <- 1..(2 * limit + 1) do
264
- get_fresh_conn() |> authorize(api_key, api_scope: "sites:read:*")
265
- end
266
-
267
- {allowed, rest} = Enum.split(conns, limit)
268
-
269
- # The first `limit` requests pass, which also confirms the higher of the
270
- # two configured limits (not the ignored lower one) is the one applied.
271
- assert Enum.all?(allowed, &(not &1.halted))
272
- assert Enum.all?(allowed, &(&1.assigns.current_user.id == user.id))
273
-
274
- assert throttled = Enum.find(rest, & &1.halted)
275
-
276
- assert json_response(throttled, 429)["error"] ==
277
- "Too many API requests. The limit is 100 per hour. Please contact us to request more capacity."
278
- end
279
-
280
- test "passes and sets current user when valid API key with required scope provided", %{
281
- conn: conn
282
- } do
283
- user = new_user()
284
- api_key = insert_api_key(unquote(key_type), user: user, scopes: ["sites:provision:*"])
285
-
286
- conn =
287
- conn
288
- |> authorize(api_key, api_scope: "sites:provision:*")
289
-
290
- refute conn.halted
291
- assert conn.assigns.current_user.id == user.id
292
- end
293
-
294
- test "passes and sets current user and site when valid API key and site ID provided", %{
295
- conn: conn
296
- } do
297
- user = new_user()
298
- site = new_site(owner: user)
299
- api_key = insert_api_key(unquote(key_type), user: user)
300
-
301
- conn =
302
- conn
303
- |> authorize(api_key, site: site, api_scope: "stats:read:*")
304
-
305
- refute conn.halted
306
- assert conn.assigns.current_user.id == user.id
307
- assert conn.assigns.site.id == site.id
308
- end
309
-
310
- @tag :ee_only
311
- test "passes for super admin user even if not a member of the requested site", %{conn: conn} do
312
- user = new_user()
313
- patch_env(:super_admin_user_ids, [user.id])
314
- site = new_site()
315
- site.team |> Ecto.Changeset.change(locked: true) |> Repo.update!()
316
- api_key = insert_api_key(unquote(key_type), user: user)
317
-
318
- conn =
319
- conn |> authorize(api_key, site: site, api_scope: "stats:read:*")
320
-
321
- refute conn.halted
322
- assert conn.assigns.current_user.id == user.id
323
- assert conn.assigns.site.id == site.id
324
- end
325
-
326
- test "passes for subscope match", %{conn: conn} do
327
- user = new_user()
328
- api_key = insert_api_key(unquote(key_type), user: user, scopes: ["funnels:*"])
329
-
330
- conn =
331
- conn
332
- |> authorize(api_key, api_scope: "funnels:read:*")
333
-
334
- refute conn.halted
335
- assert conn.assigns.current_user.id == user.id
336
- end
337
- end
338
- end
339
-
340
- describe "no context API ::" do
341
- test "legacy API key request passes validation", %{
342
- conn: conn
343
- } do
344
- legacy_api_key_user = new_user()
345
- legacy_api_key = insert_api_key(:legacy_api_key, user: legacy_api_key_user)
346
-
347
- conn =
348
- conn |> authorize(legacy_api_key, api_scope: "sites:read:*")
349
-
350
- refute conn.halted
351
- assert conn.assigns.current_user.id == legacy_api_key_user.id
352
- end
353
-
354
- test "legacy API key requests are rate limited to configured requests per hour _per user_, but their team's team-scoped keys are on an independent rate limit" do
355
- patch_env(Plausible.Auth.ApiKey,
356
- legacy_per_user_hourly_request_limit: 100,
357
- # relax burst request limit to check hourly request limit
358
- burst_request_limit: 200,
359
- burst_period_seconds: 30
360
- )
361
-
362
- legacy_api_key_user = new_user()
363
- _site = new_site(owner: legacy_api_key_user)
364
- legacy_api_key = insert_api_key(:legacy_api_key, user: legacy_api_key_user)
365
-
366
- 1..Plausible.Auth.ApiKey.legacy_hourly_request_limit()
367
- |> Enum.map(fn _ ->
368
- conn =
369
- get_fresh_conn()
370
- |> authorize(legacy_api_key, api_scope: "sites:read:*")
371
-
372
- refute conn.halted
373
- assert conn.assigns.current_user.id == legacy_api_key_user.id
374
- end)
375
-
376
- # the hardcoded limit applies per user, not per api key
377
- [legacy_api_key, insert_api_key(:legacy_api_key, user: legacy_api_key_user)]
378
- |> Enum.map(fn api_key ->
379
- conn =
380
- get_fresh_conn()
381
- |> authorize(api_key, api_scope: "sites:read:*")
382
-
383
- assert conn.halted
384
-
385
- assert json_response(conn, 429)["error"] ==
386
- "Too many API requests. The limit is 100 per hour. Please contact us to request more capacity."
387
- end)
388
-
389
- # no context API requests made with legacy API keys don't count towards team limits
390
- team_scope_api_key_user = legacy_api_key_user |> team_of() |> add_member(role: :editor)
391
-
392
- conn =
393
- get_fresh_conn()
394
- |> authorize(
395
- insert_api_key(:team_scope_api_key,
396
- user: team_scope_api_key_user
397
- ),
398
- api_scope: "sites:read:*"
399
- )
400
-
401
- refute conn.halted
402
- end
403
- end
404
-
405
- describe("site context API ::") do
406
- @tag :capture_log
407
- test "legacy API key requests pass validation for sites of all their teams",
408
- %{
409
- conn: conn
410
- } do
411
- legacy_api_key_user = new_user()
412
- legacy_api_key = insert_api_key(:legacy_api_key, user: legacy_api_key_user)
413
- site_from_first_team = new_site(owner: legacy_api_key_user)
414
- site_from_second_team = new_site()
415
- add_member(site_from_second_team.team, user: legacy_api_key_user, role: :editor)
416
-
417
- [site_from_first_team, site_from_second_team]
418
- |> Enum.map(fn site ->
419
- conn =
420
- conn
421
- |> authorize(legacy_api_key,
422
- api_context: :site,
423
- api_scope: "sites:read:*",
424
- site: site
425
- )
426
-
427
- refute conn.halted
428
- assert conn.assigns.current_user.id == legacy_api_key_user.id
429
- end)
430
- end
431
-
432
- for guest_role <- [:viewer, :editor] do
433
- @tag :capture_log
434
- test "legacy API key requests pass validation for sites where they are a guest #{guest_role} at",
435
- %{
436
- conn: conn
437
- } do
438
- legacy_api_key_user = new_user()
439
- legacy_api_key = insert_api_key(:legacy_api_key, user: legacy_api_key_user)
440
- site = new_site()
441
- add_guest(site, user: legacy_api_key_user, role: unquote(guest_role))
442
-
443
- conn =
444
- conn
445
- |> authorize(legacy_api_key,
446
- api_context: :site,
447
- api_scope: "sites:read:*",
448
- site: site
449
- )
450
-
451
- refute conn.halted
452
- assert conn.assigns.current_user.id == legacy_api_key_user.id
453
- end
454
- end
455
-
456
- for {key_type, expected_halted} <- [{:legacy_api_key, false}, {:team_scope_api_key, true}] do
457
- test "logs a warning on using #{key_type} against site with guest access", %{conn: conn} do
458
- user = new_user(team: [hourly_api_request_limit: 1])
459
- site = new_site()
460
- add_guest(site, user: user, role: :editor)
461
-
462
- api_key = insert_api_key(unquote(key_type), user: user)
463
-
464
- capture_log(fn ->
465
- conn =
466
- conn |> authorize(api_key, site: site, api_context: :site, api_scope: "sites:read:*")
467
-
468
- assert conn.halted == unquote(expected_halted)
469
- end) =~ "API key #{api_key.id} user accessing #{site.domain} as a guest"
470
- end
471
- end
472
-
473
- for {key_type, expected_halted} <- [{:legacy_api_key, true}, {:team_scope_api_key, true}] do
474
- test "logs a warning on using #{key_type} against site with no access", %{conn: conn} do
475
- user = new_user(team: [hourly_api_request_limit: 1])
476
- site = new_site()
477
-
478
- api_key = insert_api_key(unquote(key_type), user: user)
479
-
480
- capture_log(fn ->
481
- conn =
482
- conn |> authorize(api_key, site: site, api_context: :site, api_scope: "stats:read:*")
483
-
484
- assert conn.halted == unquote(expected_halted)
485
- end) =~ "API key #{api_key.id} user trying to access #{site.domain} as a non-member"
486
- end
487
- end
488
-
489
- @tag :capture_log
490
- test "legacy API key request doesn't pass validation for sites where they are not a guest at",
491
- %{
492
- conn: conn
493
- } do
494
- legacy_api_key_user = new_user()
495
- legacy_api_key = insert_api_key(:legacy_api_key, user: legacy_api_key_user)
496
- site_with_no_guests = new_site()
497
-
498
- conn =
499
- conn
500
- |> authorize(legacy_api_key,
501
- api_context: :site,
502
- api_scope: "sites:read:*",
503
- site: site_with_no_guests
504
- )
505
-
506
- assert conn.halted
507
- assert json_response(conn, 401)["error"] =~ "Invalid API key."
508
- end
509
-
510
- for guest_role <- [:viewer, :editor] do
511
- test "team-scope API key request doesn't pass validation for sites where they are a guest #{guest_role} at",
512
- %{
513
- conn: conn
514
- } do
515
- user = new_user()
516
- _site = new_site(owner: user)
517
- site = new_site()
518
- add_guest(site, user: user, role: unquote(guest_role))
519
- team_scope_api_key = insert_api_key(:team_scope_api_key, user: user)
520
-
521
- conn =
522
- conn
523
- |> authorize(
524
- team_scope_api_key,
525
- api_context: :site,
526
- api_scope: "sites:read:*",
527
- site: site
528
- )
529
-
530
- assert conn.halted
531
- assert json_response(conn, 401)["error"] =~ "Invalid API key."
532
- end
533
- end
534
-
535
- @tag :capture_log
536
- test "legacy API key requests count towards the rate limit of the team of the site" do
537
- user = new_user()
538
- legacy_api_key = insert_api_key(:legacy_api_key, user: user)
539
-
540
- team_hourly_request_limit = 5
541
- user_of_other_team = new_user(team: [hourly_api_request_limit: team_hourly_request_limit])
542
-
543
- team_scope_api_key_of_other_team =
544
- insert_api_key(:team_scope_api_key, user: user_of_other_team)
545
-
546
- site = new_site(owner: user_of_other_team)
547
-
548
- add_guest(site, user: user, role: :viewer)
549
-
550
- 1..team_hourly_request_limit
551
- |> Enum.map(fn _ ->
552
- conn =
553
- get_fresh_conn()
554
- |> authorize(legacy_api_key,
555
- api_context: :site,
556
- api_scope: "sites:read:*",
557
- site: site
558
- )
559
-
560
- refute conn.halted
561
- assert conn.assigns.current_user.id == user.id
562
- end)
563
-
564
- [
565
- legacy_api_key,
566
- team_scope_api_key_of_other_team
567
- ]
568
- |> Enum.map(fn api_key ->
569
- conn =
570
- get_fresh_conn()
571
- |> authorize(api_key,
572
- api_context: :site,
573
- api_scope: "sites:read:*",
574
- site: site
575
- )
576
-
577
- assert json_response(conn, 429)["error"] =~ "Too many API requests."
578
- assert conn.halted
579
- end)
580
- end
581
- end
582
-
583
- defp prepare_conn_for_auth(conn) do
584
- conn
585
- |> put_private(PlausibleWeb.FirstLaunchPlug, :skip)
586
- |> bypass_through(PlausibleWeb.Router)
587
- end
588
-
589
- defp authorize(conn, api_key, opts) do
590
- context = opts |> Keyword.get(:api_context)
591
- scope = opts |> Keyword.fetch!(:api_scope)
592
- site = opts |> Keyword.get(:site)
593
-
594
- conn =
595
- conn
596
- |> put_req_header("authorization", "Bearer #{api_key.key}")
597
- |> get("/", if(site, do: %{"site_id" => site.domain}, else: %{}))
598
- |> assign(:api_scope, scope)
599
-
600
- conn = if context, do: assign(conn, :api_context, context), else: conn
601
- AuthorizePublicAPI.call(conn, nil)
602
- end
603
-
604
- defp insert_api_key(:legacy_api_key, opts) do
605
- insert(:api_key, opts |> Keyword.drop([:team, :team_id]))
606
- end
607
-
608
- defp insert_api_key(:team_scope_api_key, opts) do
609
- team = opts |> Keyword.fetch!(:user) |> team_of()
610
- insert(:api_key, opts |> Keyword.put(:team, team))
611
- end
612
-
613
- defp get_fresh_conn(),
614
- do:
615
- build_conn()
616
- |> prepare_conn()
617
- |> prepare_conn_for_auth()
618
- end