Files
probo/e2e/console/rbac_test.go
Sacha Al Himdani eecbe4c46c Rename vendors to third parties
Renames the user-facing 'vendor' concept to 'third party' across the
entire codebase. The shared common_third_parties reference table is
unchanged.

Migration. Renames the vendor_category enum, the vendors and
vendor_<entity> tables (contacts, services, compliance_reports,
business_associate_agreements, data_privacy_agreements,
risk_assessments) and their vendor_id columns, the asset_vendors /
data_vendors / processing_activity_vendors junction tables,
generated_documents.vendors_document_id, the webhook_event_type
'vendor:<verb>' values, and the snapshots_type 'VENDORS' value.

Backend. Renames coredata models and SQL queries, probo services,
GraphQL / MCP API surface, console / trust / webhook resolvers and
types, the CLI (prb vendor* -> prb third-party*; pkg/cmd/vendormgmt
-> pkg/cmd/thirdpartymgmt), the document generator, vetting agent
prompts, and the common-third-parties-import command.

Frontend, packages, n8n, e2e. Renames apps/console pages, components,
hooks, routes, dialogs, and tabs; the shared @probo/vendors package
(now @probo/third-parties); the @probo/ui Vendors atoms (now
ThirdParties, VendorLogo -> ThirdPartyLogo); the n8n community node
actions/vendor folder (now actions/thirdParty); and the e2e Go test
suite (console and MCP). Filesystem and URL paths use kebab-case
(third-parties), GraphQL fields and TypeScript identifiers use
camelCase (thirdParty / thirdParties), Go types use PascalCase
(ThirdParty), and human-facing text uses 'third party' with a space.

Co-authored-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-13 21:21:39 +02:00

1443 lines
42 KiB
Go

// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// 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 }
}
}
}`
createThirdPartyMutation = `
mutation CreateThirdParty($input: CreateThirdPartyInput!) {
createThirdParty(input: $input) {
thirdPartyEdge { node { id } }
}
}`
updateThirdPartyMutation = `
mutation UpdateThirdParty($input: UpdateThirdPartyInput!) {
updateThirdParty(input: $input) {
thirdParty { id }
}
}`
deleteThirdPartyMutation = `
mutation DeleteThirdParty($input: DeleteThirdPartyInput!) {
deleteThirdParty(input: $input) {
deletedThirdPartyId
}
}`
listThirdPartiesQuery = `
query GetThirdParties($id: ID!) {
node(id: $id) {
... on Organization {
thirdParties(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()
thirdPartyID := factory.NewThirdParty(owner).WithName("RBAC Test ThirdParty").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 thirdParty",
role: "owner",
client: owner,
query: createThirdPartyMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("ThirdParty")}}
},
shouldAllow: true,
},
{
name: "admin can create thirdParty",
role: "admin",
client: admin,
query: createThirdPartyMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("ThirdParty")}}
},
shouldAllow: true,
},
{
name: "viewer cannot create thirdParty",
role: "viewer",
client: viewer,
query: createThirdPartyMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("ThirdParty")}}
},
shouldAllow: false,
},
{
name: "owner can update thirdParty",
role: "owner",
client: owner,
query: updateThirdPartyMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"id": thirdPartyID, "name": factory.SafeName("Updated ThirdParty")}}
},
shouldAllow: true,
},
{
name: "admin can update thirdParty",
role: "admin",
client: admin,
query: updateThirdPartyMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"id": thirdPartyID, "name": factory.SafeName("Updated ThirdParty")}}
},
shouldAllow: true,
},
{
name: "viewer cannot update thirdParty",
role: "viewer",
client: viewer,
query: updateThirdPartyMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"id": thirdPartyID, "name": factory.SafeName("Updated ThirdParty")}}
},
shouldAllow: false,
},
{
name: "owner can delete thirdParty",
role: "owner",
client: owner,
query: deleteThirdPartyMutation,
variables: func() map[string]any {
id := factory.NewThirdParty(owner).WithName(factory.SafeName("ToDelete")).Create()
return map[string]any{"input": map[string]any{"thirdPartyId": id}}
},
shouldAllow: true,
},
{
name: "admin can delete thirdParty",
role: "admin",
client: admin,
query: deleteThirdPartyMutation,
variables: func() map[string]any {
id := factory.NewThirdParty(owner).WithName(factory.SafeName("ToDelete")).Create()
return map[string]any{"input": map[string]any{"thirdPartyId": id}}
},
shouldAllow: true,
},
{
name: "viewer cannot delete thirdParty",
role: "viewer",
client: viewer,
query: deleteThirdPartyMutation,
variables: func() map[string]any {
id := factory.NewThirdParty(owner).WithName(factory.SafeName("ToDelete")).Create()
return map[string]any{"input": map[string]any{"thirdPartyId": id}}
},
shouldAllow: false,
},
{
name: "owner can list third parties",
role: "owner",
client: owner,
query: listThirdPartiesQuery,
variables: func() map[string]any {
return map[string]any{"id": owner.GetOrganizationID().String()}
},
shouldAllow: true,
},
{
name: "admin can list third parties",
role: "admin",
client: admin,
query: listThirdPartiesQuery,
variables: func() map[string]any {
return map[string]any{"id": owner.GetOrganizationID().String()}
},
shouldAllow: true,
},
{
name: "viewer can list third parties",
role: "viewer",
client: viewer,
query: listThirdPartiesQuery,
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)
}
},
)
}
}