The existing integer priority field represents positional ordering within a state, not semantic importance. Rename it to rank and introduce a new priority field with enum values URGENT, HIGH, MEDIUM and LOW across the entire stack. Rank is now scoped to (state, priority) so tasks are ordered within each priority group. A generated priority_rank column combines both fields into a single sortable integer for cursor pagination. Dragging a task across priority groups updates its priority automatically based on the drop position neighbors. The backend first moves the task to the new group then repositions it at the target rank. The migration defaults existing rows to MEDIUM priority and backfills ranks per (state, priority) group. Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
1127 lines
32 KiB
Go
1127 lines
32 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 }
|
|
}
|
|
}
|
|
}`
|
|
|
|
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 }
|
|
}
|
|
}
|
|
}`
|
|
|
|
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()
|
|
|
|
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, "implemented": "IMPLEMENTED"}}
|
|
},
|
|
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, "implemented": "IMPLEMENTED"}}
|
|
},
|
|
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, "implemented": "IMPLEMENTED"}}
|
|
},
|
|
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,
|
|
},
|
|
{
|
|
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)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|