From 5650150b3ff0815392abc5fad111684993eb891f Mon Sep 17 00:00:00 2001 From: Jamil Date: Mon, 24 Feb 2025 19:45:40 -0800 Subject: [PATCH] chore(portal): Enforce only `internet` resource in internet site (#8254) Currently, it would theoretically be possible for an admin to connect non-internet Resources to the Internet site. This PR fixes that by enforcing only the `internet` Resource type can belong to the `Internet` gateway group. Related: #6834 --- .../domain/resources/connection/changeset.ex | 4 ++ ...ire_internet_resource_in_internet_site.exs | 53 +++++++++++++++++++ .../domain/test/domain/resources_test.exs | 20 +++++++ 3 files changed, 77 insertions(+) create mode 100644 elixir/apps/domain/priv/repo/migrations/20250224205226_require_internet_resource_in_internet_site.exs diff --git a/elixir/apps/domain/lib/domain/resources/connection/changeset.ex b/elixir/apps/domain/lib/domain/resources/connection/changeset.ex index 5697f8d68..462a42755 100644 --- a/elixir/apps/domain/lib/domain/resources/connection/changeset.ex +++ b/elixir/apps/domain/lib/domain/resources/connection/changeset.ex @@ -22,6 +22,10 @@ defmodule Domain.Resources.Connection.Changeset do |> assoc_constraint(:resource) |> assoc_constraint(:gateway_group) |> assoc_constraint(:account) + |> check_constraint(:resource, + name: :internet_resource_in_internet_site, + message: "type must be 'internet' for the Internet site" + ) |> put_change(:account_id, account_id) end end diff --git a/elixir/apps/domain/priv/repo/migrations/20250224205226_require_internet_resource_in_internet_site.exs b/elixir/apps/domain/priv/repo/migrations/20250224205226_require_internet_resource_in_internet_site.exs new file mode 100644 index 000000000..98e448c8f --- /dev/null +++ b/elixir/apps/domain/priv/repo/migrations/20250224205226_require_internet_resource_in_internet_site.exs @@ -0,0 +1,53 @@ +defmodule Domain.Repo.Migrations.RequireInternetResourceInInternetSite do + use Ecto.Migration + + def change do + # Create a function to enforce only 'internet' resources in 'Internet' site + execute( + """ + CREATE OR REPLACE FUNCTION enforce_internet_resource_in_internet_gg() + RETURNS TRIGGER AS $$ + DECLARE + resource_type text; + site_name text; + site_managed_by text; + BEGIN + -- Fetch the resource type and gateway group details + SELECT r.type INTO resource_type + FROM resources r + WHERE r.id = NEW.resource_id; + + SELECT gg.name, gg.managed_by INTO site_name, site_managed_by + FROM gateway_groups gg + WHERE gg.id = NEW.gateway_group_id; + + -- Rule 1: Prevent non-'internet' resources in the 'Internet' gateway group + IF site_name = 'Internet' AND site_managed_by = 'system' AND resource_type != 'internet' THEN + RAISE EXCEPTION 'Only internet resource type is allowed in the Internet site' + USING ERRCODE = '23514', CONSTRAINT = 'internet_resource_in_internet_site'; + END IF; + + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + """, + """ + DROP FUNCTION IF EXISTS enforce_internet_resource_in_internet_gg(); + """ + ) + + # Create a trigger to run the check on insert or update + execute( + """ + CREATE TRIGGER internet_resource_in_internet_gg + BEFORE INSERT OR UPDATE OF resource_id, gateway_group_id + ON resource_connections + FOR EACH ROW + EXECUTE FUNCTION enforce_internet_resource_in_internet_gg(); + """, + """ + DROP TRIGGER IF EXISTS internet_resource_in_internet_gg ON resource_connections; + """ + ) + end +end diff --git a/elixir/apps/domain/test/domain/resources_test.exs b/elixir/apps/domain/test/domain/resources_test.exs index 5809b98da..e4464c691 100644 --- a/elixir/apps/domain/test/domain/resources_test.exs +++ b/elixir/apps/domain/test/domain/resources_test.exs @@ -1003,6 +1003,26 @@ defmodule Domain.ResourcesTest do end describe "create_resource/2" do + test "prevents adding other resources to the internet site", %{ + account: account, + subject: subject + } do + {:ok, gateway_group} = Domain.Gateways.create_internet_group(account) + + attrs = + Fixtures.Resources.resource_attrs( + type: :dns, + address: "example.com", + connections: [%{gateway_group_id: gateway_group.id}] + ) + + assert {:error, changeset} = create_resource(attrs, subject) + + assert %{resource: ["type must be 'internet' for the Internet site"]} in errors_on( + changeset + ).connections + end + test "returns changeset error on empty attrs", %{subject: subject} do assert {:error, changeset} = create_resource(%{}, subject)