mirror of
https://github.com/plausible/analytics.git
synced 2024-12-25 18:48:06 +03:00
790984e1ad
* Refactor and unify auth plugs for Stats and Sites APIs * Expose get site Sites API endpoint to all API keys * Test the new plug * Add test for endpoint with modified scope * Fix typos Co-authored-by: hq1 <hq@mtod.org> * Rename plug for consistency (h/t @aerosol) --------- Co-authored-by: hq1 <hq@mtod.org>
236 lines
6.9 KiB
Elixir
236 lines
6.9 KiB
Elixir
defmodule PlausibleWeb.Plugs.AuthorizePublicAPITest do
|
|
use PlausibleWeb.ConnCase, async: false
|
|
|
|
alias PlausibleWeb.Plugs.AuthorizePublicAPI
|
|
|
|
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:*")
|
|
|> AuthorizePublicAPI.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:*")
|
|
|> AuthorizePublicAPI.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:*")
|
|
|> AuthorizePublicAPI.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:*")
|
|
|> AuthorizePublicAPI.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:*")
|
|
|> AuthorizePublicAPI.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:*")
|
|
|> AuthorizePublicAPI.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:*")
|
|
|> AuthorizePublicAPI.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:*")
|
|
|> AuthorizePublicAPI.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:*")
|
|
|> AuthorizePublicAPI.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 = AuthorizePublicAPI.call(conn, nil)
|
|
second_resp = AuthorizePublicAPI.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:*")
|
|
|> AuthorizePublicAPI.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:*")
|
|
|> AuthorizePublicAPI.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:*")
|
|
|> AuthorizePublicAPI.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:*")
|
|
|> AuthorizePublicAPI.call(nil)
|
|
|
|
refute conn.halted
|
|
assert conn.assigns.current_user.id == user.id
|
|
end
|
|
end
|