From faab9e325b9f15b30408494a9aa57cd0e7c5fd38 Mon Sep 17 00:00:00 2001 From: Marcos Filipe Date: Mon, 10 Nov 2025 12:59:51 -0300 Subject: [PATCH] feat: create global enforce whitelist --- .../controllers/settings_controller.ex | 33 ++++++++ front/lib/front_web/router.ex | 2 + .../templates/settings/show.html.eex | 10 +++ .../controllers/settings_controller_test.exs | 82 +++++++++++++++++++ 4 files changed, 127 insertions(+) diff --git a/front/lib/front_web/controllers/settings_controller.ex b/front/lib/front_web/controllers/settings_controller.ex index 5364f430e..b344e9231 100644 --- a/front/lib/front_web/controllers/settings_controller.ex +++ b/front/lib/front_web/controllers/settings_controller.ex @@ -90,6 +90,39 @@ defmodule FrontWeb.SettingsController do ) end + def confirm_enforce_workflow(conn, _params) do + org_id = conn.assigns.organization_id + permissions = conn.assigns.permissions || %{} + + if Map.get(permissions, "organization.general_settings.manage", false) do + case Models.OrganizationSettings.modify(org_id, %{"enforce_whitelist" => "true"}) do + {:ok, _updated_settings} -> + conn + |> put_flash(:notice, "Whitelist enforcement applied successfully.") + |> redirect(to: settings_path(conn, :show)) + + {:error, %Ecto.Changeset{} = changeset} -> + errors = + changeset.errors |> Enum.map(fn {field, {message, _}} -> "#{field}: #{message}" end) + + conn + |> put_flash(:errors, errors) + |> put_flash(:alert, "Failed to apply whitelist enforcement.") + |> redirect(to: settings_path(conn, :show)) + + {:error, reason} -> + conn + |> put_flash(:errors, ["#{inspect(reason)}"]) + |> put_flash(:alert, "Failed to apply whitelist enforcement.") + |> redirect(to: settings_path(conn, :show)) + end + else + conn + |> put_flash(:alert, "Insufficient permissions.") + |> redirect(to: settings_path(conn, :show)) + end + end + def confirm_delete(conn, _params) do org_id = conn.assigns.organization_id diff --git a/front/lib/front_web/router.ex b/front/lib/front_web/router.ex index caf202a58..0e3fb417a 100644 --- a/front/lib/front_web/router.ex +++ b/front/lib/front_web/router.ex @@ -126,6 +126,8 @@ defmodule FrontWeb.Router do get("/settings/confirm_delete", SettingsController, :confirm_delete) + post("/settings/confirm_enforce", SettingsController, :confirm_enforce_workflow) + delete("/settings", SettingsController, :destroy) get("/jwt_config", OrganizationJWTConfigController, :show) diff --git a/front/lib/front_web/templates/settings/show.html.eex b/front/lib/front_web/templates/settings/show.html.eex index f62c83164..a5225070a 100644 --- a/front/lib/front_web/templates/settings/show.html.eex +++ b/front/lib/front_web/templates/settings/show.html.eex @@ -37,6 +37,16 @@ <% end %> +
+
Whitelist Enforcement
+

Applies new Whitelist rules to old tags and branches

+
+ <%= link "Enforce Whitelist", + to: settings_path(@conn, :confirm_enforce_workflow), + method: :post, + class: "btn btn-secondary danger" %> +
+
<%= if FeatureProvider.feature_enabled?(:multiple_organizations, param: @conn.assigns[:organization_id]) do %> <%= if @permissions["organization.delete"] do %>
diff --git a/front/test/front_web/controllers/settings_controller_test.exs b/front/test/front_web/controllers/settings_controller_test.exs index de5eab32c..2b0cdc6ae 100644 --- a/front/test/front_web/controllers/settings_controller_test.exs +++ b/front/test/front_web/controllers/settings_controller_test.exs @@ -2,6 +2,8 @@ defmodule FrontWeb.SettingsControllerTest do use FrontWeb.ConnCase alias Support.Stubs.DB + import Mock + setup %{conn: conn} do Cacheman.clear(:front) @@ -201,6 +203,86 @@ defmodule FrontWeb.SettingsControllerTest do end end + describe "POST confirm_enforce_workflow" do + test "when the user lacks manage permissions => denies the request", %{ + conn: conn, + organization_id: organization_id + } do + with_mock Front.Models.OrganizationSettings, + modify: fn ^organization_id, _ -> + send(self(), :modify_called) + {:ok, %{}} + end do + conn = + conn + |> post("/settings/confirm_enforce") + + assert redirected_to(conn) =~ "/settings" + assert get_flash(conn, :alert) == "Insufficient permissions." + refute_received :modify_called + end + end + + test "when the user can manage general settings => applies the enforcement", %{ + conn: conn, + user_id: user_id, + organization_id: organization_id + } do + Support.Stubs.PermissionPatrol.add_permissions( + organization_id, + user_id, + ["organization.view", "organization.general_settings.manage"] + ) + + with_mock Front.Models.OrganizationSettings, + modify: fn ^organization_id, %{"enforce_whitelist" => "true"} -> + send(self(), :modify_called) + {:ok, %{}} + end do + conn = + conn + |> post("/settings/confirm_enforce") + + assert redirected_to(conn) == "/settings" + assert get_flash(conn, :notice) == "Whitelist enforcement applied successfully." + assert_received :modify_called + end + end + + test "when enforcing fails => shows the error", %{ + conn: conn, + user_id: user_id, + organization_id: organization_id + } do + Support.Stubs.PermissionPatrol.add_permissions( + organization_id, + user_id, + ["organization.view", "organization.general_settings.manage"] + ) + + changeset = %Ecto.Changeset{ + valid?: false, + changes: %{}, + errors: [enforce_whitelist: {"boom", []}], + data: %{}, + types: %{} + } + + with_mock Front.Models.OrganizationSettings, + modify: fn ^organization_id, %{"enforce_whitelist" => "true"} -> + {:error, changeset} + end do + conn = + conn + |> post("/settings/confirm_enforce") + + assert redirected_to(conn) == "/settings" + assert get_flash(conn, :alert) == "Failed to apply whitelist enforcement." + assert get_flash(conn, :errors) == ["enforce_whitelist: boom"] + end + end + end + describe "DELETE destroy" do test "when everything works => redirects to me page", %{ conn: conn,