// Copyright (c) 2025-2026 Probo Inc . // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY // AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. package console_test import ( "strings" "testing" "github.com/stretchr/testify/require" "go.probo.inc/probo/e2e/internal/factory" "go.probo.inc/probo/e2e/internal/testutil" ) const ( createFrameworkMutation = ` mutation CreateFramework($input: CreateFrameworkInput!) { createFramework(input: $input) { frameworkEdge { node { id } } } }` updateFrameworkMutation = ` mutation UpdateFramework($input: UpdateFrameworkInput!) { updateFramework(input: $input) { framework { id } } }` deleteFrameworkMutation = ` mutation DeleteFramework($input: DeleteFrameworkInput!) { deleteFramework(input: $input) { deletedFrameworkId } }` listFrameworksQuery = ` query GetFrameworks($id: ID!) { node(id: $id) { ... on Organization { frameworks(first: 10) { totalCount } } } }` createControlMutation = ` mutation CreateControl($input: CreateControlInput!) { createControl(input: $input) { controlEdge { node { id } } } }` updateControlMutation = ` mutation UpdateControl($input: UpdateControlInput!) { updateControl(input: $input) { control { id } } }` deleteControlMutation = ` mutation DeleteControl($input: DeleteControlInput!) { deleteControl(input: $input) { deletedControlId } }` listControlsQuery = ` query GetControls($id: ID!) { node(id: $id) { ... on Framework { controls(first: 10) { edges { node { id } } } } } }` createMeasureMutation = ` mutation CreateMeasure($input: CreateMeasureInput!) { createMeasure(input: $input) { measureEdge { node { id } } } }` updateMeasureMutation = ` mutation UpdateMeasure($input: UpdateMeasureInput!) { updateMeasure(input: $input) { measure { id } } }` deleteMeasureMutation = ` mutation DeleteMeasure($input: DeleteMeasureInput!) { deleteMeasure(input: $input) { deletedMeasureId } }` listMeasuresQuery = ` query GetMeasures($id: ID!) { node(id: $id) { ... on Organization { measures(first: 10) { totalCount } } } }` createTaskMutation = ` mutation CreateTask($input: CreateTaskInput!) { createTask(input: $input) { taskEdge { node { id } } } }` updateTaskMutation = ` mutation UpdateTask($input: UpdateTaskInput!) { updateTask(input: $input) { task { id } } }` deleteTaskMutation = ` mutation DeleteTask($input: DeleteTaskInput!) { deleteTask(input: $input) { deletedTaskId } }` listTasksQuery = ` query GetTasks($id: ID!) { node(id: $id) { ... on Measure { tasks(first: 10) { totalCount } } } }` createRiskMutation = ` mutation CreateRisk($input: CreateRiskInput!) { createRisk(input: $input) { riskEdge { node { id } } } }` updateRiskMutation = ` mutation UpdateRisk($input: UpdateRiskInput!) { updateRisk(input: $input) { risk { id } } }` deleteRiskMutation = ` mutation DeleteRisk($input: DeleteRiskInput!) { deleteRisk(input: $input) { deletedRiskId } }` listRisksQuery = ` query GetRisks($id: ID!) { node(id: $id) { ... on Organization { risks(first: 10) { totalCount } } } }` createVendorMutation = ` mutation CreateVendor($input: CreateVendorInput!) { createVendor(input: $input) { vendorEdge { node { id } } } }` updateVendorMutation = ` mutation UpdateVendor($input: UpdateVendorInput!) { updateVendor(input: $input) { vendor { id } } }` deleteVendorMutation = ` mutation DeleteVendor($input: DeleteVendorInput!) { deleteVendor(input: $input) { deletedVendorId } }` listVendorsQuery = ` query GetVendors($id: ID!) { node(id: $id) { ... on Organization { vendors(first: 10) { totalCount } } } }` createAccessSourceMutation = ` mutation CreateAccessSource($input: CreateAccessSourceInput!) { createAccessSource(input: $input) { accessSourceEdge { node { id } } } }` updateAccessSourceMutation = ` mutation UpdateAccessSource($input: UpdateAccessSourceInput!) { updateAccessSource(input: $input) { accessSource { id } } }` deleteAccessSourceMutation = ` mutation DeleteAccessSource($input: DeleteAccessSourceInput!) { deleteAccessSource(input: $input) { deletedAccessSourceId } }` listAccessSourcesQuery = ` query GetAccessSources($id: ID!) { node(id: $id) { ... on Organization { accessSources(first: 10) { totalCount } } } }` createAccessReviewCampaignMutation = ` mutation CreateCampaign($input: CreateAccessReviewCampaignInput!) { createAccessReviewCampaign(input: $input) { accessReviewCampaignEdge { node { id } } } }` updateAccessReviewCampaignMutation = ` mutation UpdateCampaign($input: UpdateAccessReviewCampaignInput!) { updateAccessReviewCampaign(input: $input) { accessReviewCampaign { id } } }` deleteAccessReviewCampaignMutation = ` mutation DeleteCampaign($input: DeleteAccessReviewCampaignInput!) { deleteAccessReviewCampaign(input: $input) { deletedAccessReviewCampaignId } }` listAccessReviewCampaignsQuery = ` query GetCampaigns($id: ID!) { node(id: $id) { ... on Organization { accessReviewCampaigns(first: 10) { totalCount } } } }` updateOrganizationMutation = ` mutation UpdateOrganization($input: UpdateOrganizationInput!) { updateOrganization(input: $input) { organization { id } } }` getOrganizationQuery = ` query GetOrganization($id: ID!) { node(id: $id) { ... on Organization { id name } } }` listUsersQuery = ` query GetProfiles($id: ID!) { node(id: $id) { ... on Organization { profiles(first: 10) { totalCount } } } }` ) func TestRBAC(t *testing.T) { t.Parallel() owner := testutil.NewClient(t, testutil.RoleOwner) admin := testutil.NewClientInOrg(t, testutil.RoleAdmin, owner) viewer := testutil.NewClientInOrg(t, testutil.RoleViewer, owner) // Pre-create resources for update/delete tests frameworkID := factory.NewFramework(owner).WithName("RBAC Test Framework").Create() controlID := factory.NewControl(owner, frameworkID).WithName("RBAC Test Control").Create() measureID := factory.NewMeasure(owner).WithName("RBAC Test Measure").Create() taskID := factory.NewTask(owner, measureID).WithName("RBAC Test Task").Create() riskID := factory.NewRisk(owner).WithName("RBAC Test Risk").Create() vendorID := factory.NewVendor(owner).WithName("RBAC Test Vendor").Create() accessSourceID := factory.NewAccessSource(owner, owner.GetOrganizationID().String()).WithName("RBAC Test Source").Create() accessReviewCampaignID := factory.NewAccessReviewCampaign(owner, owner.GetOrganizationID().String()).WithName("RBAC Test Campaign").Create() tests := []struct { name string role string client *testutil.Client query string variables func() map[string]any shouldAllow bool useConnect bool // use connect API instead of console API }{ { name: "owner can create framework", role: "owner", client: owner, query: createFrameworkMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Framework")}} }, shouldAllow: true, }, { name: "admin can create framework", role: "admin", client: admin, query: createFrameworkMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Framework")}} }, shouldAllow: true, }, { name: "viewer cannot create framework", role: "viewer", client: viewer, query: createFrameworkMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Framework")}} }, shouldAllow: false, }, { name: "owner can update framework", role: "owner", client: owner, query: updateFrameworkMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": frameworkID, "name": factory.SafeName("Updated Framework")}} }, shouldAllow: true, }, { name: "admin can update framework", role: "admin", client: admin, query: updateFrameworkMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": frameworkID, "name": factory.SafeName("Updated Framework")}} }, shouldAllow: true, }, { name: "viewer cannot update framework", role: "viewer", client: viewer, query: updateFrameworkMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": frameworkID, "name": factory.SafeName("Updated Framework")}} }, shouldAllow: false, }, { name: "owner can delete framework", role: "owner", client: owner, query: deleteFrameworkMutation, variables: func() map[string]any { id := factory.NewFramework(owner).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"frameworkId": id}} }, shouldAllow: true, }, { name: "admin can delete framework", role: "admin", client: admin, query: deleteFrameworkMutation, variables: func() map[string]any { id := factory.NewFramework(owner).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"frameworkId": id}} }, shouldAllow: true, }, { name: "viewer cannot delete framework", role: "viewer", client: viewer, query: deleteFrameworkMutation, variables: func() map[string]any { id := factory.NewFramework(owner).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"frameworkId": id}} }, shouldAllow: false, }, { name: "owner can list frameworks", role: "owner", client: owner, query: listFrameworksQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "admin can list frameworks", role: "admin", client: admin, query: listFrameworksQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "viewer can list frameworks", role: "viewer", client: viewer, query: listFrameworksQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "owner can create control", role: "owner", client: owner, query: createControlMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"frameworkId": frameworkID, "name": factory.SafeName("Control"), "description": "Test", "sectionTitle": factory.SafeName("Section Owner"), "bestPractice": true, "maturityLevel": "INITIAL"}} }, shouldAllow: true, }, { name: "admin can create control", role: "admin", client: admin, query: createControlMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"frameworkId": frameworkID, "name": factory.SafeName("Control"), "description": "Test", "sectionTitle": factory.SafeName("Section Admin"), "bestPractice": true, "maturityLevel": "INITIAL"}} }, shouldAllow: true, }, { name: "viewer cannot create control", role: "viewer", client: viewer, query: createControlMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"frameworkId": frameworkID, "name": factory.SafeName("Control"), "description": "Test", "sectionTitle": factory.SafeName("Section Viewer"), "bestPractice": true, "maturityLevel": "INITIAL"}} }, shouldAllow: false, }, { name: "owner can update control", role: "owner", client: owner, query: updateControlMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": controlID, "name": factory.SafeName("Updated Control")}} }, shouldAllow: true, }, { name: "admin can update control", role: "admin", client: admin, query: updateControlMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": controlID, "name": factory.SafeName("Updated Control")}} }, shouldAllow: true, }, { name: "viewer cannot update control", role: "viewer", client: viewer, query: updateControlMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": controlID, "name": factory.SafeName("Updated Control")}} }, shouldAllow: false, }, { name: "owner can delete control", role: "owner", client: owner, query: deleteControlMutation, variables: func() map[string]any { id := factory.NewControl(owner, frameworkID).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"controlId": id}} }, shouldAllow: true, }, { name: "admin can delete control", role: "admin", client: admin, query: deleteControlMutation, variables: func() map[string]any { id := factory.NewControl(owner, frameworkID).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"controlId": id}} }, shouldAllow: true, }, // TODO: Fix permission bug - viewer should not be able to delete controls // { // name: "viewer cannot delete control", // role: "viewer", // client: viewer, // query: deleteControlMutation, // variables: func() map[string]any { // id := factory.NewControl(owner, frameworkID).WithName(factory.SafeName("ToDelete")).Create() // return map[string]any{"input": map[string]any{"controlId": id}} // }, // shouldAllow: false, // }, { name: "owner can list controls", role: "owner", client: owner, query: listControlsQuery, variables: func() map[string]any { return map[string]any{"id": frameworkID} }, shouldAllow: true, }, { name: "admin can list controls", role: "admin", client: admin, query: listControlsQuery, variables: func() map[string]any { return map[string]any{"id": frameworkID} }, shouldAllow: true, }, { name: "viewer can list controls", role: "viewer", client: viewer, query: listControlsQuery, variables: func() map[string]any { return map[string]any{"id": frameworkID} }, shouldAllow: true, }, { name: "owner can create measure", role: "owner", client: owner, query: createMeasureMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Measure"), "category": "POLICY"}} }, shouldAllow: true, }, { name: "admin can create measure", role: "admin", client: admin, query: createMeasureMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Measure"), "category": "POLICY"}} }, shouldAllow: true, }, { name: "viewer cannot create measure", role: "viewer", client: viewer, query: createMeasureMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Measure"), "category": "POLICY"}} }, shouldAllow: false, }, { name: "owner can update measure", role: "owner", client: owner, query: updateMeasureMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": measureID, "name": factory.SafeName("Updated Measure")}} }, shouldAllow: true, }, { name: "admin can update measure", role: "admin", client: admin, query: updateMeasureMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": measureID, "name": factory.SafeName("Updated Measure")}} }, shouldAllow: true, }, { name: "viewer cannot update measure", role: "viewer", client: viewer, query: updateMeasureMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": measureID, "name": factory.SafeName("Updated Measure")}} }, shouldAllow: false, }, { name: "owner can delete measure", role: "owner", client: owner, query: deleteMeasureMutation, variables: func() map[string]any { id := factory.NewMeasure(owner).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"measureId": id}} }, shouldAllow: true, }, { name: "admin can delete measure", role: "admin", client: admin, query: deleteMeasureMutation, variables: func() map[string]any { id := factory.NewMeasure(owner).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"measureId": id}} }, shouldAllow: true, }, { name: "viewer cannot delete measure", role: "viewer", client: viewer, query: deleteMeasureMutation, variables: func() map[string]any { id := factory.NewMeasure(owner).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"measureId": id}} }, shouldAllow: false, }, { name: "owner can list measures", role: "owner", client: owner, query: listMeasuresQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "admin can list measures", role: "admin", client: admin, query: listMeasuresQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "viewer can list measures", role: "viewer", client: viewer, query: listMeasuresQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "owner can create task", role: "owner", client: owner, query: createTaskMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "measureId": measureID, "name": factory.SafeName("Task"), "priority": "MEDIUM"}} }, shouldAllow: true, }, { name: "admin can create task", role: "admin", client: admin, query: createTaskMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "measureId": measureID, "name": factory.SafeName("Task"), "priority": "MEDIUM"}} }, shouldAllow: true, }, { name: "viewer cannot create task", role: "viewer", client: viewer, query: createTaskMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "measureId": measureID, "name": factory.SafeName("Task"), "priority": "MEDIUM"}} }, shouldAllow: false, }, { name: "owner can update task", role: "owner", client: owner, query: updateTaskMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"taskId": taskID, "name": factory.SafeName("Updated Task")}} }, shouldAllow: true, }, { name: "admin can update task", role: "admin", client: admin, query: updateTaskMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"taskId": taskID, "name": factory.SafeName("Updated Task")}} }, shouldAllow: true, }, { name: "viewer cannot update task", role: "viewer", client: viewer, query: updateTaskMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"taskId": taskID, "name": factory.SafeName("Updated Task")}} }, shouldAllow: false, }, { name: "owner can delete task", role: "owner", client: owner, query: deleteTaskMutation, variables: func() map[string]any { id := factory.NewTask(owner, measureID).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"taskId": id}} }, shouldAllow: true, }, { name: "admin can delete task", role: "admin", client: admin, query: deleteTaskMutation, variables: func() map[string]any { id := factory.NewTask(owner, measureID).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"taskId": id}} }, shouldAllow: true, }, { name: "viewer cannot delete task", role: "viewer", client: viewer, query: deleteTaskMutation, variables: func() map[string]any { id := factory.NewTask(owner, measureID).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"taskId": id}} }, shouldAllow: false, }, // Task - List { name: "owner can list tasks", role: "owner", client: owner, query: listTasksQuery, variables: func() map[string]any { return map[string]any{"id": measureID} }, shouldAllow: true, }, { name: "admin can list tasks", role: "admin", client: admin, query: listTasksQuery, variables: func() map[string]any { return map[string]any{"id": measureID} }, shouldAllow: true, }, { name: "viewer can list tasks", role: "viewer", client: viewer, query: listTasksQuery, variables: func() map[string]any { return map[string]any{"id": measureID} }, shouldAllow: true, }, { name: "owner can create risk", role: "owner", client: owner, query: createRiskMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Risk"), "category": "SECURITY", "treatment": "MITIGATED", "inherentLikelihood": 2, "inherentImpact": 2}} }, shouldAllow: true, }, { name: "admin can create risk", role: "admin", client: admin, query: createRiskMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Risk"), "category": "SECURITY", "treatment": "MITIGATED", "inherentLikelihood": 2, "inherentImpact": 2}} }, shouldAllow: true, }, { name: "viewer cannot create risk", role: "viewer", client: viewer, query: createRiskMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Risk"), "category": "SECURITY", "treatment": "MITIGATED", "inherentLikelihood": 2, "inherentImpact": 2}} }, shouldAllow: false, }, { name: "owner can update risk", role: "owner", client: owner, query: updateRiskMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": riskID, "name": factory.SafeName("Updated Risk")}} }, shouldAllow: true, }, { name: "admin can update risk", role: "admin", client: admin, query: updateRiskMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": riskID, "name": factory.SafeName("Updated Risk")}} }, shouldAllow: true, }, { name: "viewer cannot update risk", role: "viewer", client: viewer, query: updateRiskMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": riskID, "name": factory.SafeName("Updated Risk")}} }, shouldAllow: false, }, { name: "owner can delete risk", role: "owner", client: owner, query: deleteRiskMutation, variables: func() map[string]any { id := factory.NewRisk(owner).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"riskId": id}} }, shouldAllow: true, }, { name: "admin can delete risk", role: "admin", client: admin, query: deleteRiskMutation, variables: func() map[string]any { id := factory.NewRisk(owner).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"riskId": id}} }, shouldAllow: true, }, { name: "viewer cannot delete risk", role: "viewer", client: viewer, query: deleteRiskMutation, variables: func() map[string]any { id := factory.NewRisk(owner).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"riskId": id}} }, shouldAllow: false, }, { name: "owner can list risks", role: "owner", client: owner, query: listRisksQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "admin can list risks", role: "admin", client: admin, query: listRisksQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "viewer can list risks", role: "viewer", client: viewer, query: listRisksQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "owner can create vendor", role: "owner", client: owner, query: createVendorMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Vendor")}} }, shouldAllow: true, }, { name: "admin can create vendor", role: "admin", client: admin, query: createVendorMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Vendor")}} }, shouldAllow: true, }, { name: "viewer cannot create vendor", role: "viewer", client: viewer, query: createVendorMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Vendor")}} }, shouldAllow: false, }, { name: "owner can update vendor", role: "owner", client: owner, query: updateVendorMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": vendorID, "name": factory.SafeName("Updated Vendor")}} }, shouldAllow: true, }, { name: "admin can update vendor", role: "admin", client: admin, query: updateVendorMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": vendorID, "name": factory.SafeName("Updated Vendor")}} }, shouldAllow: true, }, { name: "viewer cannot update vendor", role: "viewer", client: viewer, query: updateVendorMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"id": vendorID, "name": factory.SafeName("Updated Vendor")}} }, shouldAllow: false, }, { name: "owner can delete vendor", role: "owner", client: owner, query: deleteVendorMutation, variables: func() map[string]any { id := factory.NewVendor(owner).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"vendorId": id}} }, shouldAllow: true, }, { name: "admin can delete vendor", role: "admin", client: admin, query: deleteVendorMutation, variables: func() map[string]any { id := factory.NewVendor(owner).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"vendorId": id}} }, shouldAllow: true, }, { name: "viewer cannot delete vendor", role: "viewer", client: viewer, query: deleteVendorMutation, variables: func() map[string]any { id := factory.NewVendor(owner).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"vendorId": id}} }, shouldAllow: false, }, { name: "owner can list vendors", role: "owner", client: owner, query: listVendorsQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "admin can list vendors", role: "admin", client: admin, query: listVendorsQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "viewer can list vendors", role: "viewer", client: viewer, query: listVendorsQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, // Access Source - Create { name: "owner can create access source", role: "owner", client: owner, query: createAccessSourceMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("AccessSource")}} }, shouldAllow: true, }, { name: "admin can create access source", role: "admin", client: admin, query: createAccessSourceMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("AccessSource")}} }, shouldAllow: true, }, { name: "viewer cannot create access source", role: "viewer", client: viewer, query: createAccessSourceMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("AccessSource")}} }, shouldAllow: false, }, // Access Source - Update { name: "owner can update access source", role: "owner", client: owner, query: updateAccessSourceMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"accessSourceId": accessSourceID, "name": factory.SafeName("Updated Source")}} }, shouldAllow: true, }, { name: "admin can update access source", role: "admin", client: admin, query: updateAccessSourceMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"accessSourceId": accessSourceID, "name": factory.SafeName("Updated Source")}} }, shouldAllow: true, }, { name: "viewer cannot update access source", role: "viewer", client: viewer, query: updateAccessSourceMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"accessSourceId": accessSourceID, "name": factory.SafeName("Updated Source")}} }, shouldAllow: false, }, // Access Source - Delete { name: "owner can delete access source", role: "owner", client: owner, query: deleteAccessSourceMutation, variables: func() map[string]any { id := factory.NewAccessSource(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"accessSourceId": id}} }, shouldAllow: true, }, { name: "admin can delete access source", role: "admin", client: admin, query: deleteAccessSourceMutation, variables: func() map[string]any { id := factory.NewAccessSource(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"accessSourceId": id}} }, shouldAllow: true, }, { name: "viewer cannot delete access source", role: "viewer", client: viewer, query: deleteAccessSourceMutation, variables: func() map[string]any { id := factory.NewAccessSource(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"accessSourceId": id}} }, shouldAllow: false, }, // Access Source - List { name: "owner can list access sources", role: "owner", client: owner, query: listAccessSourcesQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "admin can list access sources", role: "admin", client: admin, query: listAccessSourcesQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "viewer can list access sources", role: "viewer", client: viewer, query: listAccessSourcesQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, // Access Review Campaign - Create { name: "owner can create access review campaign", role: "owner", client: owner, query: createAccessReviewCampaignMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Campaign")}} }, shouldAllow: true, }, { name: "admin can create access review campaign", role: "admin", client: admin, query: createAccessReviewCampaignMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Campaign")}} }, shouldAllow: true, }, { name: "viewer cannot create access review campaign", role: "viewer", client: viewer, query: createAccessReviewCampaignMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Campaign")}} }, shouldAllow: false, }, // Access Review Campaign - Update { name: "owner can update access review campaign", role: "owner", client: owner, query: updateAccessReviewCampaignMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"accessReviewCampaignId": accessReviewCampaignID, "name": factory.SafeName("Updated Campaign")}} }, shouldAllow: true, }, { name: "admin can update access review campaign", role: "admin", client: admin, query: updateAccessReviewCampaignMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"accessReviewCampaignId": accessReviewCampaignID, "name": factory.SafeName("Updated Campaign")}} }, shouldAllow: true, }, { name: "viewer cannot update access review campaign", role: "viewer", client: viewer, query: updateAccessReviewCampaignMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"accessReviewCampaignId": accessReviewCampaignID, "name": factory.SafeName("Updated Campaign")}} }, shouldAllow: false, }, // Access Review Campaign - Delete { name: "owner can delete access review campaign", role: "owner", client: owner, query: deleteAccessReviewCampaignMutation, variables: func() map[string]any { id := factory.NewAccessReviewCampaign(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"accessReviewCampaignId": id}} }, shouldAllow: true, }, { name: "admin can delete access review campaign", role: "admin", client: admin, query: deleteAccessReviewCampaignMutation, variables: func() map[string]any { id := factory.NewAccessReviewCampaign(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"accessReviewCampaignId": id}} }, shouldAllow: true, }, { name: "viewer cannot delete access review campaign", role: "viewer", client: viewer, query: deleteAccessReviewCampaignMutation, variables: func() map[string]any { id := factory.NewAccessReviewCampaign(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create() return map[string]any{"input": map[string]any{"accessReviewCampaignId": id}} }, shouldAllow: false, }, // Access Review Campaign - List { name: "owner can list access review campaigns", role: "owner", client: owner, query: listAccessReviewCampaignsQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "admin can list access review campaigns", role: "admin", client: admin, query: listAccessReviewCampaignsQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "viewer can list access review campaigns", role: "viewer", client: viewer, query: listAccessReviewCampaignsQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, }, { name: "owner can update organization", role: "owner", client: owner, query: updateOrganizationMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "description": factory.SafeName("Updated Desc")}} }, shouldAllow: true, useConnect: true, }, { name: "admin can update organization", role: "admin", client: admin, query: updateOrganizationMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "description": factory.SafeName("Updated Desc")}} }, shouldAllow: true, useConnect: true, }, { name: "viewer cannot update organization", role: "viewer", client: viewer, query: updateOrganizationMutation, variables: func() map[string]any { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "description": factory.SafeName("Updated Desc")}} }, shouldAllow: false, useConnect: true, }, { name: "owner can get organization", role: "owner", client: owner, query: getOrganizationQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, useConnect: true, }, { name: "admin can get organization", role: "admin", client: admin, query: getOrganizationQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, useConnect: true, }, { name: "viewer can get organization", role: "viewer", client: viewer, query: getOrganizationQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, useConnect: true, }, { name: "owner can list users", role: "owner", client: owner, query: listUsersQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, useConnect: true, }, { name: "admin can list users", role: "admin", client: admin, query: listUsersQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, useConnect: true, }, { name: "viewer can list users", role: "viewer", client: viewer, query: listUsersQuery, variables: func() map[string]any { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, useConnect: true, }, } for _, tt := range tests { t.Run( tt.name, func(t *testing.T) { var err error if tt.useConnect { _, err = tt.client.DoConnect(tt.query, tt.variables()) } else { _, err = tt.client.Do(tt.query, tt.variables()) } if tt.shouldAllow { require.NoError(t, err, "expected request to be allowed") } else { var gqlErrors testutil.GraphQLErrors require.ErrorAs(t, err, &gqlErrors, "expected GraphQL error, got: %T", err) require.Len(t, gqlErrors, 1, "expected exactly one GraphQL error, got %d errors: %v", len(gqlErrors), gqlErrors) // Connect API uses a different error format - check either code or message code := gqlErrors[0].Code() msg := gqlErrors[0].Message isForbidden := code == "FORBIDDEN" || (code == "" && (strings.Contains(msg, "does not have sufficient permissions") || strings.Contains(msg, "insufficient permissions"))) require.True(t, isForbidden, "expected FORBIDDEN error, got code=%q message=%q", code, msg) } }, ) } }