Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog/7754-fix-viewer-assigned-system-readonly.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type: Fixed
description: Fixed viewer users being unable to edit systems assigned to them due to an ungated read-only permission check
pr: 7754
labels: []
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Regression test for ENG-3137: Viewer user can't edit a system assigned to them
*
* A viewer user with an assigned system should be able to edit that system.
* The RBAC commit (8e9788e) introduced a read-only check that only looks at
* the global SYSTEM_UPDATE scope, ignoring SYSTEM_MANAGER_UPDATE which
* allows per-system editing for assigned systems.
*/
import {
stubDatasetCrud,
stubPlus,
stubSystemCrud,
stubSystemIntegrations,
stubSystemVendors,
stubTaxonomyEntities,
} from "cypress/support/stubs";

import { SYSTEM_ROUTE } from "~/features/common/nav/routes";
import { RoleRegistryEnum, ScopeRegistryEnum } from "~/types/api";

describe("ENG-3137: Viewer with assigned system should be able to edit", () => {
beforeEach(() => {
cy.login();
cy.overrideFeatureFlag("alphaRbac", true);
stubSystemCrud();
stubTaxonomyEntities();
stubPlus(true);
cy.intercept("GET", "/api/v1/system", {
fixture: "systems/systems.json",
}).as("getSystems");
cy.intercept({ method: "POST", url: "/api/v1/system*" }).as(
"postDictSystem",
);
cy.intercept("/api/v1/config?api_set=false", {});
stubDatasetCrud();
stubSystemIntegrations();
stubSystemVendors();
});

it("viewer with system_manager:update can edit assigned system", () => {
// Simulate a viewer who also has system_manager:update for their assigned system
cy.fixture("login.json").then((body) => {
const { id: userId } = body.user_data;
cy.intercept(`/api/v1/user/${userId}/permission`, {
body: {
id: userId,
user_id: userId,
roles: [RoleRegistryEnum.VIEWER],
total_scopes: [
// Standard viewer scopes
ScopeRegistryEnum.SYSTEM_READ,
ScopeRegistryEnum.SYSTEM_MANAGER_READ,
ScopeRegistryEnum.SYSTEM_MANAGER_UPDATE,
// Other viewer scopes
ScopeRegistryEnum.USER_READ,
ScopeRegistryEnum.ORGANIZATION_READ,
],
},
}).as("getUserPermission");
});

cy.visit(`${SYSTEM_ROUTE}/configure/demo_analytics_system`);
cy.wait("@getUserPermission");

// The form should NOT be read-only for a viewer with system_manager:update
cy.getByTestId("input-name").should("exist");

// Regression guard (ENG-3137): read-only alert should NOT appear
cy.contains("Read-only access").should("not.exist");

// Regression guard (ENG-3137): form fields should be editable, not disabled
cy.get("fieldset[disabled]").should("not.exist");
});

it("viewer WITHOUT system_manager:update sees read-only form", () => {
// Standard viewer without system_manager:update
cy.assumeRole(RoleRegistryEnum.VIEWER);

cy.visit(`${SYSTEM_ROUTE}/configure/demo_analytics_system`);

cy.getByTestId("input-name").should("exist");

// This viewer SHOULD see read-only since they have no update permissions
cy.contains("Read-only access").should("exist");
cy.get("fieldset[disabled]").should("exist");
});
});
15 changes: 11 additions & 4 deletions clients/admin-ui/src/features/system/SystemInformationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
useCustomFields,
} from "~/features/common/custom-fields";
import { LegacyResourceTypes } from "~/features/common/custom-fields/types";
import { useFlags } from "~/features/common/features";
import { useFeatures } from "~/features/common/features/features.slice";
import { ControlledSelect } from "~/features/common/form/ControlledSelect";
import { CustomSwitch, CustomTextInput } from "~/features/common/form/inputs";
Expand Down Expand Up @@ -86,10 +87,16 @@ const SystemInformationForm = ({
}: Props) => {
const { data: systems = [] } = useGetAllSystemsQuery();
const { plus: systemGroupsEnabled } = useFeatures();

// Check if user has permission to update systems
const canUpdateSystems = useHasPermission([ScopeRegistryEnum.SYSTEM_UPDATE]);
const isReadOnly = passedInSystem && !canUpdateSystems;
const { flags } = useFlags();

// Check if user has permission to update systems (only when RBAC is enabled)
// Checks both global SYSTEM_UPDATE and per-system SYSTEM_MANAGER_UPDATE
// so that viewers with assigned systems can still edit them
const canUpdateSystems = useHasPermission([
ScopeRegistryEnum.SYSTEM_UPDATE,
ScopeRegistryEnum.SYSTEM_MANAGER_UPDATE,
]);
const isReadOnly = flags.alphaRbac && passedInSystem && !canUpdateSystems;

const dispatch = useAppDispatch();

Expand Down
Loading