From fb8ffde119803e45735999211b0162c3f18b8b50 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sun, 8 Feb 2026 13:24:48 -0600 Subject: [PATCH] fix: prevent site assignment when sites are disabled for org When an organization has sites disabled (use_sites=false), the API now automatically removes any site_id from device creation requests, ensuring devices are assigned directly to the organization instead. This prevents Terraform (and other API clients) from creating devices in sites when sites functionality is disabled for the organization. Fixes issue where Terraform could bypass site restrictions via API. --- .../controllers/api/v1/devices_controller.ex | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/towerops_web/controllers/api/v1/devices_controller.ex b/lib/towerops_web/controllers/api/v1/devices_controller.ex index 01cf289e..5c3a9c00 100644 --- a/lib/towerops_web/controllers/api/v1/devices_controller.ex +++ b/lib/towerops_web/controllers/api/v1/devices_controller.ex @@ -8,6 +8,7 @@ defmodule ToweropsWeb.Api.V1.DevicesController do use ToweropsWeb, :controller alias Towerops.Devices + alias Towerops.Organizations alias Towerops.Workers.DiscoveryWorker @doc """ @@ -88,7 +89,10 @@ defmodule ToweropsWeb.Api.V1.DevicesController do organization_id = conn.assigns.current_organization_id current_user = conn.assigns[:current_user] - device_params = default_organization_id(device_params, organization_id) + device_params = + device_params + |> default_organization_id(organization_id) + |> maybe_strip_site_id_if_sites_disabled(organization_id) case verify_site_access(device_params["site_id"], organization_id) do :ok -> @@ -247,6 +251,18 @@ defmodule ToweropsWeb.Api.V1.DevicesController do end end + defp maybe_strip_site_id_if_sites_disabled(device_params, organization_id) do + organization = Organizations.get_organization!(organization_id) + + # If sites are disabled and a site_id was provided, remove it + # This ensures devices are assigned directly to the organization + if !organization.use_sites && device_params["site_id"] do + Map.delete(device_params, "site_id") + else + device_params + end + end + defp create_and_discover_device(conn, device_params, current_user) do opts = if current_user && current_user.is_superuser, do: [bypass_limits: true], else: []