diff --git a/lib/query_canary/checks.ex b/lib/query_canary/checks.ex index a0060b3..60a1dce 100644 --- a/lib/query_canary/checks.ex +++ b/lib/query_canary/checks.ex @@ -10,6 +10,7 @@ defmodule QueryCanary.Checks do alias QueryCanary.Checks.{Check, CheckResult} alias QueryCanary.Accounts.{User, Scope, TeamUser} + alias QueryCanary.Accounts alias QueryCanary.Connections.ConnectionManager alias QueryCanary.Checks.CheckNotifier @@ -37,29 +38,41 @@ defmodule QueryCanary.Checks do # Public, we can view no matter what def can_perform?(:view, _, %QueryCanary.Checks.Check{public: true}), do: true - # If not public, only if we're the scoped user + # If not public, only if we're the scoped user or member of owning team def can_perform?( :view, %QueryCanary.Accounts.Scope{} = scope, %QueryCanary.Checks.Check{} = check ) do - server_ids = QueryCanary.Servers.list_servers(scope) |> Enum.map(fn s -> s.id end) + check = Repo.preload(check, server: [:team]) - check.server_id in server_ids + cond do + is_nil(check.server.team_id) -> + check.user_id == scope.user.id + + true -> + Accounts.user_has_access_to_team?(scope.user.id, check.server.team_id) + end end # No one can edit def can_perform?(:edit, nil, _), do: false - # Unless you are the scoped user + # Unless you are the owner or team member (for edit restrict maybe admin) def can_perform?( :edit, %QueryCanary.Accounts.Scope{} = scope, %QueryCanary.Checks.Check{} = check ) do - server_ids = QueryCanary.Servers.list_servers(scope) |> Enum.map(fn s -> s.id end) + check = Repo.preload(check, server: [:team]) - check.server_id in server_ids + cond do + is_nil(check.server.team_id) -> + check.user_id == scope.user.id + + true -> + Accounts.user_has_access_to_team?(scope.user.id, check.server.team_id) + end end def can_perform?(_, _, _), do: false diff --git a/lib/query_canary_web/check_auth.ex b/lib/query_canary_web/check_auth.ex index 2d4d1f3..73a2a95 100644 --- a/lib/query_canary_web/check_auth.ex +++ b/lib/query_canary_web/check_auth.ex @@ -4,7 +4,7 @@ defmodule QueryCanaryWeb.CheckAuth do alias QueryCanary.Checks - def on_mount(:default, %{"id" => check_id}, _session, socket) do + def on_mount(:view, %{"id" => check_id}, _session, socket) do with %QueryCanary.Checks.Check{} = check <- Checks.get_possibly_public_check(check_id), true <- Checks.can_perform?(:view, socket.assigns.current_scope, check) do {:cont, assign(socket, :check, check)} @@ -16,4 +16,17 @@ defmodule QueryCanaryWeb.CheckAuth do |> redirect(to: "/")} end end + + def on_mount(:edit, %{"id" => check_id}, _session, socket) do + with %QueryCanary.Checks.Check{} = check <- Checks.get_possibly_public_check(check_id), + true <- Checks.can_perform?(:edit, socket.assigns.current_scope, check) do + {:cont, assign(socket, :check, check)} + else + _ -> + {:halt, + socket + |> put_flash(:error, "You don't have permission to access this check!") + |> redirect(to: "/")} + end + end end diff --git a/lib/query_canary_web/live/check_live/form.ex b/lib/query_canary_web/live/check_live/form.ex index 5a4e33d..c12771b 100644 --- a/lib/query_canary_web/live/check_live/form.ex +++ b/lib/query_canary_web/live/check_live/form.ex @@ -4,6 +4,8 @@ defmodule QueryCanaryWeb.CheckLive.Form do alias QueryCanary.Checks alias QueryCanary.Checks.Check + on_mount {QueryCanaryWeb.CheckAuth, :edit} + @impl true def render(assigns) do ~H""" diff --git a/lib/query_canary_web/live/check_live/show.ex b/lib/query_canary_web/live/check_live/show.ex index 8efb439..7d18607 100644 --- a/lib/query_canary_web/live/check_live/show.ex +++ b/lib/query_canary_web/live/check_live/show.ex @@ -6,7 +6,7 @@ defmodule QueryCanaryWeb.CheckLive.Show do import QueryCanaryWeb.Components.CheckAnalysis - on_mount QueryCanaryWeb.CheckAuth + on_mount {QueryCanaryWeb.CheckAuth, :view} @impl true def render(assigns) do diff --git a/test/query_canary_web/live/check_live_test.exs b/test/query_canary_web/live/check_live_test.exs index 0230ef9..b4cfd6c 100644 --- a/test/query_canary_web/live/check_live_test.exs +++ b/test/query_canary_web/live/check_live_test.exs @@ -3,6 +3,7 @@ defmodule QueryCanaryWeb.CheckLiveTest do import Phoenix.LiveViewTest import QueryCanary.ChecksFixtures + import QueryCanary.AccountsFixtures @update_attrs %{ name: "Updated Check", @@ -100,4 +101,27 @@ defmodule QueryCanaryWeb.CheckLiveTest do assert html =~ "Updated Check" end end + + describe "Permissions" do + test "cannot view another user's private check", %{conn: conn, scope: _scope} do + other_scope = user_scope_fixture() + other_check = check_fixture(other_scope) + + # Attempt to load other user's check + assert {:error, {:redirect, %{to: redirected_to}}} = live(conn, ~p"/checks/#{other_check}") + # expecting redirect to index or auth page + assert redirected_to =~ "/" + end + + test "cannot edit another user's check", %{conn: conn, scope: _scope} do + other_scope = user_scope_fixture() + other_check = check_fixture(other_scope) + + # Try direct edit route + assert {:error, {:redirect, %{to: redirected_to}}} = + live(conn, ~p"/checks/#{other_check}/edit") + + assert redirected_to =~ "/" + end + end end