fix: M1 — prevent cross-org resource existence probing via scoped fetch
Use Repo.get_by(schema, id: id, organization_id: organization_id) instead of Repo.get + pattern match so that resources from wrong orgs return :not_found instead of :forbidden, preventing org membership discovery.
This commit is contained in:
parent
c473f472c7
commit
095c5d3236
14 changed files with 42 additions and 192 deletions
11
bugs.md
11
bugs.md
|
|
@ -59,17 +59,6 @@
|
|||
|
||||
## MEDIUM
|
||||
|
||||
### M1. Resource Scoping Reveals Org Membership
|
||||
|
||||
**File:** `lib/towerops_web/scoped_resource.ex:15-20`
|
||||
|
||||
**Severity:** MEDIUM — Cross-org resource existence probing
|
||||
|
||||
**Description:** Returns `:forbidden` for wrong-org resources vs `:not_found` for nonexistent. Organization A can probe if a specific resource ID belongs to Organization B.
|
||||
|
||||
**Fix:** Return `:not_found` in both cases — use `Repo.get_by(schema, id: id, organization_id: organization_id)`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
### M4. Stripe Webhook Signature Not Enforced at Plug Level
|
||||
|
|
|
|||
|
|
@ -110,11 +110,6 @@ defmodule ToweropsWeb.Api.V1.ChecksController do
|
|||
{:ok, check} ->
|
||||
json(conn, format_check(check))
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this check"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -142,11 +137,6 @@ defmodule ToweropsWeb.Api.V1.ChecksController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this check"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -180,11 +170,6 @@ defmodule ToweropsWeb.Api.V1.ChecksController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this check"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ defmodule ToweropsWeb.Api.V1.DevicesController do
|
|||
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: reason})
|
||||
end
|
||||
end
|
||||
|
|
@ -144,11 +144,6 @@ defmodule ToweropsWeb.Api.V1.DevicesController do
|
|||
{:ok, device} ->
|
||||
json(conn, format_device_details(device))
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this device"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -192,11 +187,6 @@ defmodule ToweropsWeb.Api.V1.DevicesController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this device"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -235,11 +225,6 @@ defmodule ToweropsWeb.Api.V1.DevicesController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this device"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -280,7 +265,6 @@ defmodule ToweropsWeb.Api.V1.DevicesController do
|
|||
defp verify_site_access(site_id, organization_id) do
|
||||
case ScopedResource.fetch(Site, site_id, organization_id) do
|
||||
{:ok, _site} -> :ok
|
||||
{:error, :forbidden} -> {:error, "Access denied to this site"}
|
||||
{:error, :not_found} -> {:error, "Site not found"}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -67,11 +67,6 @@ defmodule ToweropsWeb.Api.V1.EscalationPoliciesController do
|
|||
{:ok, policy} ->
|
||||
json(conn, format_policy_detail(policy))
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this escalation policy"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -95,11 +90,6 @@ defmodule ToweropsWeb.Api.V1.EscalationPoliciesController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this escalation policy"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -129,11 +119,6 @@ defmodule ToweropsWeb.Api.V1.EscalationPoliciesController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this escalation policy"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -163,11 +148,6 @@ defmodule ToweropsWeb.Api.V1.EscalationPoliciesController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this escalation policy"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -191,9 +171,6 @@ defmodule ToweropsWeb.Api.V1.EscalationPoliciesController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
else
|
||||
{:error, :forbidden} ->
|
||||
conn |> put_status(:forbidden) |> json(%{error: "Access denied to this escalation policy"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn |> put_status(:not_found) |> json(%{error: "Escalation policy not found"})
|
||||
|
||||
|
|
@ -211,9 +188,6 @@ defmodule ToweropsWeb.Api.V1.EscalationPoliciesController do
|
|||
{:ok, _} = OnCall.delete_escalation_rule(rule)
|
||||
json(conn, %{success: true})
|
||||
else
|
||||
{:error, :forbidden} ->
|
||||
conn |> put_status(:forbidden) |> json(%{error: "Access denied to this escalation policy"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn |> put_status(:not_found) |> json(%{error: "Escalation policy not found"})
|
||||
|
||||
|
|
@ -244,9 +218,6 @@ defmodule ToweropsWeb.Api.V1.EscalationPoliciesController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
else
|
||||
{:error, :forbidden} ->
|
||||
conn |> put_status(:forbidden) |> json(%{error: "Access denied to this escalation policy"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn |> put_status(:not_found) |> json(%{error: "Escalation policy not found"})
|
||||
|
||||
|
|
@ -273,9 +244,6 @@ defmodule ToweropsWeb.Api.V1.EscalationPoliciesController do
|
|||
conn |> put_status(:internal_server_error) |> json(%{error: "Failed to delete escalation target"})
|
||||
end
|
||||
else
|
||||
{:error, :forbidden} ->
|
||||
conn |> put_status(:forbidden) |> json(%{error: "Access denied to this escalation policy"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn |> put_status(:not_found) |> json(%{error: "Escalation policy not found"})
|
||||
|
||||
|
|
|
|||
|
|
@ -71,11 +71,6 @@ defmodule ToweropsWeb.Api.V1.MaintenanceWindowsController do
|
|||
{:ok, window} ->
|
||||
json(conn, format_window(window))
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this maintenance window"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -99,11 +94,6 @@ defmodule ToweropsWeb.Api.V1.MaintenanceWindowsController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this maintenance window"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -133,11 +123,6 @@ defmodule ToweropsWeb.Api.V1.MaintenanceWindowsController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this maintenance window"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
|
|||
|
|
@ -68,11 +68,6 @@ defmodule ToweropsWeb.Api.V1.SchedulesController do
|
|||
{:ok, schedule} ->
|
||||
json(conn, format_schedule_detail(schedule))
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this schedule"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -96,11 +91,6 @@ defmodule ToweropsWeb.Api.V1.SchedulesController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this schedule"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -130,11 +120,6 @@ defmodule ToweropsWeb.Api.V1.SchedulesController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this schedule"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -153,11 +138,6 @@ defmodule ToweropsWeb.Api.V1.SchedulesController do
|
|||
user = OnCall.who_is_on_call(id)
|
||||
json(conn, %{on_call: format_user(user)})
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this schedule"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -187,11 +167,6 @@ defmodule ToweropsWeb.Api.V1.SchedulesController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this schedule"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -215,9 +190,6 @@ defmodule ToweropsWeb.Api.V1.SchedulesController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
else
|
||||
{:error, :forbidden} ->
|
||||
conn |> put_status(:forbidden) |> json(%{error: "Access denied to this schedule"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn |> put_status(:not_found) |> json(%{error: "Schedule not found"})
|
||||
|
||||
|
|
@ -235,9 +207,6 @@ defmodule ToweropsWeb.Api.V1.SchedulesController do
|
|||
{:ok, _} = OnCall.delete_layer(layer)
|
||||
json(conn, %{success: true})
|
||||
else
|
||||
{:error, :forbidden} ->
|
||||
conn |> put_status(:forbidden) |> json(%{error: "Access denied to this schedule"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn |> put_status(:not_found) |> json(%{error: "Schedule not found"})
|
||||
|
||||
|
|
@ -289,9 +258,6 @@ defmodule ToweropsWeb.Api.V1.SchedulesController do
|
|||
{:ok, _} = OnCall.remove_layer_member(member)
|
||||
json(conn, %{success: true})
|
||||
else
|
||||
{:error, :forbidden} ->
|
||||
conn |> put_status(:forbidden) |> json(%{error: "Access denied to this schedule"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn |> put_status(:not_found) |> json(%{error: "Schedule not found"})
|
||||
|
||||
|
|
@ -325,9 +291,6 @@ defmodule ToweropsWeb.Api.V1.SchedulesController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn |> put_status(:forbidden) |> json(%{error: "Access denied to this schedule"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn |> put_status(:not_found) |> json(%{error: "Schedule not found"})
|
||||
end
|
||||
|
|
@ -348,9 +311,6 @@ defmodule ToweropsWeb.Api.V1.SchedulesController do
|
|||
conn |> put_status(:internal_server_error) |> json(%{error: "Failed to delete override"})
|
||||
end
|
||||
else
|
||||
{:error, :forbidden} ->
|
||||
conn |> put_status(:forbidden) |> json(%{error: "Access denied to this schedule"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn |> put_status(:not_found) |> json(%{error: "Schedule not found"})
|
||||
|
||||
|
|
|
|||
|
|
@ -87,11 +87,6 @@ defmodule ToweropsWeb.Api.V1.SitesController do
|
|||
{:ok, site} ->
|
||||
json(conn, format_site(site))
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this site"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -119,11 +114,6 @@ defmodule ToweropsWeb.Api.V1.SitesController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this site"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
@ -157,11 +147,6 @@ defmodule ToweropsWeb.Api.V1.SitesController do
|
|||
|> json(%{errors: translate_errors(changeset)})
|
||||
end
|
||||
|
||||
{:error, :forbidden} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Access denied to this site"})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
|
|||
|
|
@ -8,15 +8,9 @@ defmodule ToweropsWeb.ScopedResource do
|
|||
@spec fetch(module(), Ecto.UUID.t(), Ecto.UUID.t()) ::
|
||||
{:ok, struct()} | {:error, :not_found | :forbidden}
|
||||
def fetch(schema, id, organization_id) do
|
||||
case Repo.get(schema, id) do
|
||||
nil ->
|
||||
{:error, :not_found}
|
||||
|
||||
%{organization_id: ^organization_id} = resource ->
|
||||
{:ok, resource}
|
||||
|
||||
%{organization_id: _other_org_id} ->
|
||||
{:error, :forbidden}
|
||||
case Repo.get_by(schema, id: id, organization_id: organization_id) do
|
||||
nil -> {:error, :not_found}
|
||||
resource -> {:ok, resource}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -297,14 +297,14 @@ defmodule ToweropsWeb.Api.V1.ChecksControllerTest do
|
|||
assert %{"error" => "Check not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when check belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when check belongs to different organization", %{conn: conn} do
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
other_check = create_check(other_org.id)
|
||||
|
||||
conn = get(conn, ~p"/api/v1/checks/#{other_check.id}")
|
||||
|
||||
assert %{"error" => "Access denied to this check"} = json_response(conn, 403)
|
||||
assert %{"error" => "Check not found"} = json_response(conn, 404)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -342,7 +342,7 @@ defmodule ToweropsWeb.Api.V1.ChecksControllerTest do
|
|||
assert %{"error" => "Check not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when check belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when check belongs to different organization", %{conn: conn} do
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
other_check = create_check(other_org.id)
|
||||
|
|
@ -351,7 +351,7 @@ defmodule ToweropsWeb.Api.V1.ChecksControllerTest do
|
|||
|
||||
conn = patch(conn, ~p"/api/v1/checks/#{other_check.id}", params)
|
||||
|
||||
assert %{"error" => "Access denied to this check"} = json_response(conn, 403)
|
||||
assert %{"error" => "Check not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 422 with invalid params", %{conn: conn, organization: organization} do
|
||||
|
|
@ -391,14 +391,14 @@ defmodule ToweropsWeb.Api.V1.ChecksControllerTest do
|
|||
assert %{"error" => "Check not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when check belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when check belongs to different organization", %{conn: conn} do
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
other_check = create_check(other_org.id)
|
||||
|
||||
conn = delete(conn, ~p"/api/v1/checks/#{other_check.id}")
|
||||
|
||||
assert %{"error" => "Access denied to this check"} = json_response(conn, 403)
|
||||
assert %{"error" => "Check not found"} = json_response(conn, 404)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ defmodule ToweropsWeb.Api.V1.DevicesControllerTest do
|
|||
assert org_id == organization.id
|
||||
end
|
||||
|
||||
test "returns 403 when site belongs to different organization", %{conn: conn, organization: organization} do
|
||||
test "returns 404 when site belongs to different organization", %{conn: conn, organization: organization} do
|
||||
# Enable sites for the authenticated organization
|
||||
{:ok, _organization} = Towerops.Organizations.update_organization(organization, %{use_sites: true})
|
||||
|
||||
|
|
@ -204,10 +204,10 @@ defmodule ToweropsWeb.Api.V1.DevicesControllerTest do
|
|||
|
||||
conn = post(conn, ~p"/api/v1/devices", device_params)
|
||||
|
||||
assert %{"error" => "Access denied to this site"} = json_response(conn, 403)
|
||||
assert %{"error" => "Site not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when site_id does not exist", %{conn: conn, organization: organization} do
|
||||
test "returns 404 when site_id does not exist", %{conn: conn, organization: organization} do
|
||||
# Enable sites for this organization
|
||||
{:ok, _organization} = Towerops.Organizations.update_organization(organization, %{use_sites: true})
|
||||
|
||||
|
|
@ -221,7 +221,7 @@ defmodule ToweropsWeb.Api.V1.DevicesControllerTest do
|
|||
|
||||
conn = post(conn, ~p"/api/v1/devices", device_params)
|
||||
|
||||
assert %{"error" => "Site not found"} = json_response(conn, 403)
|
||||
assert %{"error" => "Site not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 422 with invalid device params", %{conn: conn} do
|
||||
|
|
@ -277,7 +277,7 @@ defmodule ToweropsWeb.Api.V1.DevicesControllerTest do
|
|||
assert %{"error" => "Device not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when device belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when device belongs to different organization", %{conn: conn} do
|
||||
# Create another organization with a device
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
|
|
@ -285,7 +285,7 @@ defmodule ToweropsWeb.Api.V1.DevicesControllerTest do
|
|||
|
||||
conn = get(conn, ~p"/api/v1/devices/#{other_device.id}")
|
||||
|
||||
assert %{"error" => "Access denied to this device"} = json_response(conn, 403)
|
||||
assert %{"error" => "Device not found"} = json_response(conn, 404)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -323,7 +323,7 @@ defmodule ToweropsWeb.Api.V1.DevicesControllerTest do
|
|||
assert %{"error" => "Device not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when device belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when device belongs to different organization", %{conn: conn} do
|
||||
# Create another organization with a device
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
|
|
@ -337,7 +337,7 @@ defmodule ToweropsWeb.Api.V1.DevicesControllerTest do
|
|||
|
||||
conn = patch(conn, ~p"/api/v1/devices/#{other_device.id}", update_params)
|
||||
|
||||
assert %{"error" => "Access denied to this device"} = json_response(conn, 403)
|
||||
assert %{"error" => "Device not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 422 with invalid update params", %{conn: conn, organization: organization} do
|
||||
|
|
@ -388,7 +388,7 @@ defmodule ToweropsWeb.Api.V1.DevicesControllerTest do
|
|||
assert %{"error" => "Device not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when device belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when device belongs to different organization", %{conn: conn} do
|
||||
# Create another organization with a device
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
|
|
@ -396,7 +396,7 @@ defmodule ToweropsWeb.Api.V1.DevicesControllerTest do
|
|||
|
||||
conn = delete(conn, ~p"/api/v1/devices/#{other_device.id}")
|
||||
|
||||
assert %{"error" => "Access denied to this device"} = json_response(conn, 403)
|
||||
assert %{"error" => "Device not found"} = json_response(conn, 404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -132,14 +132,14 @@ defmodule ToweropsWeb.Api.V1.EscalationPoliciesControllerTest do
|
|||
assert %{"error" => "Escalation policy not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when policy belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when policy belongs to different organization", %{conn: conn} do
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
other_policy = escalation_policy_fixture(other_org.id)
|
||||
|
||||
conn = get(conn, ~p"/api/v1/escalation_policies/#{other_policy.id}")
|
||||
|
||||
assert %{"error" => "Access denied to this escalation policy"} = json_response(conn, 403)
|
||||
assert %{"error" => "Escalation policy not found"} = json_response(conn, 404)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ defmodule ToweropsWeb.Api.V1.EscalationPoliciesControllerTest do
|
|||
assert %{"error" => "Escalation policy not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when policy belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when policy belongs to different organization", %{conn: conn} do
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
other_policy = escalation_policy_fixture(other_org.id)
|
||||
|
|
@ -168,7 +168,7 @@ defmodule ToweropsWeb.Api.V1.EscalationPoliciesControllerTest do
|
|||
params = %{"escalation_policy" => %{"name" => "Hacked"}}
|
||||
conn = patch(conn, ~p"/api/v1/escalation_policies/#{other_policy.id}", params)
|
||||
|
||||
assert %{"error" => "Access denied to this escalation policy"} = json_response(conn, 403)
|
||||
assert %{"error" => "Escalation policy not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 400 when escalation_policy parameter is missing", %{
|
||||
|
|
@ -199,14 +199,14 @@ defmodule ToweropsWeb.Api.V1.EscalationPoliciesControllerTest do
|
|||
assert %{"error" => "Escalation policy not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when policy belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when policy belongs to different organization", %{conn: conn} do
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
other_policy = escalation_policy_fixture(other_org.id)
|
||||
|
||||
conn = delete(conn, ~p"/api/v1/escalation_policies/#{other_policy.id}")
|
||||
|
||||
assert %{"error" => "Access denied to this escalation policy"} = json_response(conn, 403)
|
||||
assert %{"error" => "Escalation policy not found"} = json_response(conn, 404)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ defmodule ToweropsWeb.Api.V1.MaintenanceWindowsControllerTest do
|
|||
assert %{"error" => "Maintenance window not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when window belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when window belongs to different organization", %{conn: conn} do
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
|
||||
|
|
@ -176,7 +176,7 @@ defmodule ToweropsWeb.Api.V1.MaintenanceWindowsControllerTest do
|
|||
|
||||
conn = get(conn, ~p"/api/v1/maintenance_windows/#{other_window.id}")
|
||||
|
||||
assert %{"error" => "Access denied to this maintenance window"} = json_response(conn, 403)
|
||||
assert %{"error" => "Maintenance window not found"} = json_response(conn, 404)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -242,7 +242,7 @@ defmodule ToweropsWeb.Api.V1.MaintenanceWindowsControllerTest do
|
|||
assert %{"error" => "Maintenance window not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when window belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when window belongs to different organization", %{conn: conn} do
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
|
||||
|
|
@ -254,7 +254,7 @@ defmodule ToweropsWeb.Api.V1.MaintenanceWindowsControllerTest do
|
|||
|
||||
conn = delete(conn, ~p"/api/v1/maintenance_windows/#{other_window.id}")
|
||||
|
||||
assert %{"error" => "Access denied to this maintenance window"} = json_response(conn, 403)
|
||||
assert %{"error" => "Maintenance window not found"} = json_response(conn, 404)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -137,14 +137,14 @@ defmodule ToweropsWeb.Api.V1.SchedulesControllerTest do
|
|||
assert %{"error" => "Schedule not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when schedule belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when schedule belongs to different organization", %{conn: conn} do
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
other_schedule = schedule_fixture(other_org.id)
|
||||
|
||||
conn = get(conn, ~p"/api/v1/schedules/#{other_schedule.id}")
|
||||
|
||||
assert %{"error" => "Access denied to this schedule"} = json_response(conn, 403)
|
||||
assert %{"error" => "Schedule not found"} = json_response(conn, 404)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ defmodule ToweropsWeb.Api.V1.SchedulesControllerTest do
|
|||
assert %{"error" => "Schedule not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when schedule belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when schedule belongs to different organization", %{conn: conn} do
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
other_schedule = schedule_fixture(other_org.id)
|
||||
|
|
@ -173,7 +173,7 @@ defmodule ToweropsWeb.Api.V1.SchedulesControllerTest do
|
|||
params = %{"schedule" => %{"name" => "Hacked"}}
|
||||
conn = patch(conn, ~p"/api/v1/schedules/#{other_schedule.id}", params)
|
||||
|
||||
assert %{"error" => "Access denied to this schedule"} = json_response(conn, 403)
|
||||
assert %{"error" => "Schedule not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 400 when schedule parameter is missing", %{
|
||||
|
|
@ -204,14 +204,14 @@ defmodule ToweropsWeb.Api.V1.SchedulesControllerTest do
|
|||
assert %{"error" => "Schedule not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when schedule belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when schedule belongs to different organization", %{conn: conn} do
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
other_schedule = schedule_fixture(other_org.id)
|
||||
|
||||
conn = delete(conn, ~p"/api/v1/schedules/#{other_schedule.id}")
|
||||
|
||||
assert %{"error" => "Access denied to this schedule"} = json_response(conn, 403)
|
||||
assert %{"error" => "Schedule not found"} = json_response(conn, 404)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ defmodule ToweropsWeb.Api.V1.SitesControllerTest do
|
|||
assert %{"error" => "Site not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when site belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when site belongs to different organization", %{conn: conn} do
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
|
||||
|
|
@ -182,7 +182,7 @@ defmodule ToweropsWeb.Api.V1.SitesControllerTest do
|
|||
|
||||
conn = get(conn, ~p"/api/v1/sites/#{other_site.id}")
|
||||
|
||||
assert %{"error" => "Access denied to this site"} = json_response(conn, 403)
|
||||
assert %{"error" => "Site not found"} = json_response(conn, 404)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -225,7 +225,7 @@ defmodule ToweropsWeb.Api.V1.SitesControllerTest do
|
|||
assert %{"error" => "Site not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when site belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when site belongs to different organization", %{conn: conn} do
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
|
||||
|
|
@ -240,7 +240,7 @@ defmodule ToweropsWeb.Api.V1.SitesControllerTest do
|
|||
|
||||
conn = patch(conn, ~p"/api/v1/sites/#{other_site.id}", update_params)
|
||||
|
||||
assert %{"error" => "Access denied to this site"} = json_response(conn, 403)
|
||||
assert %{"error" => "Site not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 422 with invalid update params", %{conn: conn, organization: organization} do
|
||||
|
|
@ -293,7 +293,7 @@ defmodule ToweropsWeb.Api.V1.SitesControllerTest do
|
|||
assert %{"error" => "Site not found"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "returns 403 when site belongs to different organization", %{conn: conn} do
|
||||
test "returns 404 when site belongs to different organization", %{conn: conn} do
|
||||
other_user = user_fixture()
|
||||
other_org = organization_fixture(other_user.id)
|
||||
|
||||
|
|
@ -302,7 +302,7 @@ defmodule ToweropsWeb.Api.V1.SitesControllerTest do
|
|||
|
||||
conn = delete(conn, ~p"/api/v1/sites/#{other_site.id}")
|
||||
|
||||
assert %{"error" => "Access denied to this site"} = json_response(conn, 403)
|
||||
assert %{"error" => "Site not found"} = json_response(conn, 404)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue