-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
235 additions
and
0 deletions.
There are no files selected for viewing
235 changes: 235 additions & 0 deletions
235
test/plausible_web/plugs/authorize_public_api_plug_test.exs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
defmodule PlausibleWeb.AuthorizePublicApiPlugTest do | ||
use PlausibleWeb.ConnCase, async: false | ||
|
||
alias PlausibleWeb.AuthorizePublicApiPlug | ||
|
||
setup %{conn: conn} do | ||
conn = | ||
conn | ||
|> put_private(PlausibleWeb.FirstLaunchPlug, :skip) | ||
|> bypass_through(PlausibleWeb.Router) | ||
|
||
{:ok, conn: conn} | ||
end | ||
|
||
test "halts with error when bearer token is missing", %{conn: conn} do | ||
conn = | ||
conn | ||
|> get("/") | ||
|> assign(:api_scope, "stats:read:*") | ||
|> AuthorizePublicApiPlug.call(nil) | ||
|
||
assert conn.halted | ||
assert json_response(conn, 401)["error"] =~ "Missing API key." | ||
end | ||
|
||
test "halts with error when bearer token is invalid against read-only Stats API", %{conn: conn} do | ||
conn = | ||
conn | ||
|> put_req_header("authorization", "Bearer invalid") | ||
|> get("/") | ||
|> assign(:api_scope, "stats:read:*") | ||
|> AuthorizePublicApiPlug.call(nil) | ||
|
||
assert conn.halted | ||
assert json_response(conn, 401)["error"] =~ "Invalid API key or site ID." | ||
end | ||
|
||
test "halts with error when bearer token is invalid", %{conn: conn} do | ||
conn = | ||
conn | ||
|> put_req_header("authorization", "Bearer invalid") | ||
|> get("/") | ||
|> assign(:api_scope, "sites:provision:*") | ||
|> AuthorizePublicApiPlug.call(nil) | ||
|
||
assert conn.halted | ||
assert json_response(conn, 401)["error"] =~ "Invalid API key." | ||
end | ||
|
||
test "halts with error on missing site ID when request made to Stats API", %{conn: conn} do | ||
api_key = insert(:api_key, user: build(:user)) | ||
|
||
conn = | ||
conn | ||
|> put_req_header("authorization", "Bearer #{api_key.key}") | ||
|> get("/") | ||
|> assign(:api_scope, "stats:read:*") | ||
|> AuthorizePublicApiPlug.call(nil) | ||
|
||
assert conn.halted | ||
assert json_response(conn, 400)["error"] =~ "Missing site ID." | ||
end | ||
|
||
@tag :ee_only | ||
test "halts with error when upgrade is required", %{conn: conn} do | ||
user = insert(:user, trial_expiry_date: nil) | ||
site = insert(:site, members: [user]) | ||
api_key = insert(:api_key, user: user) | ||
|
||
conn = | ||
conn | ||
|> put_req_header("authorization", "Bearer #{api_key.key}") | ||
|> get("/", %{"site_id" => site.domain}) | ||
|> assign(:api_scope, "stats:read:*") | ||
|> AuthorizePublicApiPlug.call(nil) | ||
|
||
assert conn.halted | ||
|
||
assert json_response(conn, 402)["error"] =~ | ||
"The account that owns this API key does not have access" | ||
end | ||
|
||
test "halts with error when site is locked", %{conn: conn} do | ||
user = insert(:user) | ||
site = insert(:site, members: [user], locked: true) | ||
api_key = insert(:api_key, user: user) | ||
|
||
conn = | ||
conn | ||
|> put_req_header("authorization", "Bearer #{api_key.key}") | ||
|> get("/", %{"site_id" => site.domain}) | ||
|> assign(:api_scope, "stats:read:*") | ||
|> AuthorizePublicApiPlug.call(nil) | ||
|
||
assert conn.halted | ||
assert json_response(conn, 402)["error"] =~ "This Plausible site is locked" | ||
end | ||
|
||
test "halts with error when site ID is invalid", %{conn: conn} do | ||
user = insert(:user) | ||
_site = insert(:site, members: [user]) | ||
api_key = insert(:api_key, user: user) | ||
|
||
conn = | ||
conn | ||
|> put_req_header("authorization", "Bearer #{api_key.key}") | ||
|> get("/", %{"site_id" => "invalid.domain"}) | ||
|> assign(:api_scope, "stats:read:*") | ||
|> AuthorizePublicApiPlug.call(nil) | ||
|
||
assert conn.halted | ||
assert json_response(conn, 401)["error"] =~ "Invalid API key or site ID." | ||
end | ||
|
||
test "halts with error when API key owner does not have access to the requested site", %{ | ||
conn: conn | ||
} do | ||
user = insert(:user) | ||
site = insert(:site) | ||
api_key = insert(:api_key, user: user) | ||
|
||
conn = | ||
conn | ||
|> put_req_header("authorization", "Bearer #{api_key.key}") | ||
|> get("/", %{"site_id" => site.domain}) | ||
|> assign(:api_scope, "stats:read:*") | ||
|> AuthorizePublicApiPlug.call(nil) | ||
|
||
assert conn.halted | ||
assert json_response(conn, 401)["error"] =~ "Invalid API key or site ID." | ||
end | ||
|
||
test "halts with error when API lacks required scope", %{conn: conn} do | ||
user = insert(:user) | ||
api_key = insert(:api_key, user: user) | ||
|
||
conn = | ||
conn | ||
|> put_req_header("authorization", "Bearer #{api_key.key}") | ||
|> get("/") | ||
|> assign(:api_scope, "sites:provision:*") | ||
|> AuthorizePublicApiPlug.call(nil) | ||
|
||
assert conn.halted | ||
assert json_response(conn, 401)["error"] =~ "Invalid API key." | ||
end | ||
|
||
test "halts with error when API rate limit hit", %{conn: conn} do | ||
user = insert(:user) | ||
api_key = insert(:api_key, user: user, hourly_request_limit: 1) | ||
|
||
conn = | ||
conn | ||
|> put_req_header("authorization", "Bearer #{api_key.key}") | ||
|> get("/") | ||
|> assign(:api_scope, "sites:read:*") | ||
|
||
first_resp = AuthorizePublicApiPlug.call(conn, nil) | ||
second_resp = AuthorizePublicApiPlug.call(conn, nil) | ||
|
||
refute first_resp.halted | ||
assert second_resp.halted | ||
assert json_response(second_resp, 429)["error"] =~ "Too many API requests." | ||
end | ||
|
||
test "passes and sets current user when valid API key with required scope provided", %{ | ||
conn: conn | ||
} do | ||
user = insert(:user) | ||
api_key = insert(:api_key, user: user, scopes: ["sites:provision:*"]) | ||
|
||
conn = | ||
conn | ||
|> put_req_header("authorization", "Bearer #{api_key.key}") | ||
|> get("/") | ||
|> assign(:api_scope, "sites:provision:*") | ||
|> AuthorizePublicApiPlug.call(nil) | ||
|
||
refute conn.halted | ||
assert conn.assigns.current_user.id == user.id | ||
end | ||
|
||
test "passes and sets current user and site when valid API key and site ID provided", %{ | ||
conn: conn | ||
} do | ||
user = insert(:user) | ||
site = insert(:site, members: [user]) | ||
api_key = insert(:api_key, user: user) | ||
|
||
conn = | ||
conn | ||
|> put_req_header("authorization", "Bearer #{api_key.key}") | ||
|> get("/", %{"site_id" => site.domain}) | ||
|> assign(:api_scope, "stats:read:*") | ||
|> AuthorizePublicApiPlug.call(nil) | ||
|
||
refute conn.halted | ||
assert conn.assigns.current_user.id == user.id | ||
assert conn.assigns.site.id == site.id | ||
end | ||
|
||
@tag :ee_only | ||
test "passes for super admin user even if not a member of the requested site", %{conn: conn} do | ||
user = insert(:user) | ||
patch_env(:super_admin_user_ids, [user.id]) | ||
site = insert(:site, locked: true) | ||
api_key = insert(:api_key, user: user) | ||
|
||
conn = | ||
conn | ||
|> put_req_header("authorization", "Bearer #{api_key.key}") | ||
|> get("/", %{"site_id" => site.domain}) | ||
|> assign(:api_scope, "stats:read:*") | ||
|> AuthorizePublicApiPlug.call(nil) | ||
|
||
refute conn.halted | ||
assert conn.assigns.current_user.id == user.id | ||
assert conn.assigns.site.id == site.id | ||
end | ||
|
||
test "passes for subscope match", %{conn: conn} do | ||
user = insert(:user) | ||
api_key = insert(:api_key, user: user, scopes: ["funnels:*"]) | ||
|
||
conn = | ||
conn | ||
|> put_req_header("authorization", "Bearer #{api_key.key}") | ||
|> get("/") | ||
|> assign(:api_scope, "funnels:read:*") | ||
|> AuthorizePublicApiPlug.call(nil) | ||
|
||
refute conn.halted | ||
assert conn.assigns.current_user.id == user.id | ||
end | ||
end |