Fix SOA permissions handling + add scope conditions

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-01-21 13:30:41 +04:00
parent ff65303a1a
commit 7ce73bbcfa
12 changed files with 272 additions and 201 deletions

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<7f57391fcdf09c326854f1cc2e0008c2>> * @generated SignedSource<<3db987ede3a3aa1dfa570e76a00ef224>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -15,6 +15,7 @@ export type FrameworkDetailPageFragment$data = {
readonly canCreateControl: boolean; readonly canCreateControl: boolean;
readonly canDelete: boolean; readonly canDelete: boolean;
readonly canExport: boolean; readonly canExport: boolean;
readonly canGenerateSOA: boolean;
readonly canUpdate: boolean; readonly canUpdate: boolean;
readonly controls: { readonly controls: {
readonly __id: string; readonly __id: string;
@@ -140,6 +141,19 @@ return {
"name": "permission", "name": "permission",
"storageKey": "permission(action:\"core:control:create\")" "storageKey": "permission(action:\"core:control:create\")"
}, },
{
"alias": "canGenerateSOA",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:framework:generate-state-of-applicability"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:framework:generate-state-of-applicability\")"
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -247,6 +261,6 @@ return {
}; };
})(); })();
(node as any).hash = "c88994d5f1872bfe21cfe65e1911b76a"; (node as any).hash = "a3eb8b9d80c69d7faa83eae88e3b986b";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<bbc7e1cc0c5c3f72d0297ff14a1dff77>> * @generated SignedSource<<a493a708fe8353db1159647a455582ad>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -189,6 +189,19 @@ return {
"name": "permission", "name": "permission",
"storageKey": "permission(action:\"core:control:create\")" "storageKey": "permission(action:\"core:control:create\")"
}, },
{
"alias": "canGenerateSOA",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:framework:generate-state-of-applicability"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:framework:generate-state-of-applicability\")"
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -301,12 +314,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "2a82ae6447b93b0fa6b570571694052b", "cacheID": "58485d98106169267addab7729095131",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "FrameworkGraphNodeQuery", "name": "FrameworkGraphNodeQuery",
"operationKind": "query", "operationKind": "query",
"text": "query FrameworkGraphNodeQuery(\n $frameworkId: ID!\n) {\n node(id: $frameworkId) {\n __typename\n ... on Framework {\n id\n name\n ...FrameworkDetailPageFragment\n }\n id\n }\n}\n\nfragment FrameworkDetailPageFragment on Framework {\n id\n name\n description\n lightLogoURL\n darkLogoURL\n canExport: permission(action: \"core:franework:export\")\n canUpdate: permission(action: \"core:framework:update\")\n canDelete: permission(action: \"core:framework:delete\")\n canCreateControl: permission(action: \"core:control:create\")\n organization {\n name\n id\n }\n controls(first: 250, orderBy: {field: SECTION_TITLE, direction: ASC}) {\n edges {\n node {\n id\n sectionTitle\n name\n status\n exclusionJustification\n bestPractice\n }\n }\n }\n}\n" "text": "query FrameworkGraphNodeQuery(\n $frameworkId: ID!\n) {\n node(id: $frameworkId) {\n __typename\n ... on Framework {\n id\n name\n ...FrameworkDetailPageFragment\n }\n id\n }\n}\n\nfragment FrameworkDetailPageFragment on Framework {\n id\n name\n description\n lightLogoURL\n darkLogoURL\n canExport: permission(action: \"core:franework:export\")\n canUpdate: permission(action: \"core:framework:update\")\n canDelete: permission(action: \"core:framework:delete\")\n canCreateControl: permission(action: \"core:control:create\")\n canGenerateSOA: permission(action: \"core:framework:generate-state-of-applicability\")\n organization {\n name\n id\n }\n controls(first: 250, orderBy: {field: SECTION_TITLE, direction: ASC}) {\n edges {\n node {\n id\n sectionTitle\n name\n status\n exclusionJustification\n bestPractice\n }\n }\n }\n}\n"
} }
}; };
})(); })();

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<f3564b51cac1482be5c6c4443a9030cf>> * @generated SignedSource<<4c350f48aeb8f51c9716aed4eeadc735>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -16,6 +16,7 @@ export type StateOfApplicabilityGraphNodeQuery$variables = {
export type StateOfApplicabilityGraphNodeQuery$data = { export type StateOfApplicabilityGraphNodeQuery$data = {
readonly node: { readonly node: {
readonly canDelete?: boolean; readonly canDelete?: boolean;
readonly canExport?: boolean;
readonly canUpdate?: boolean; readonly canUpdate?: boolean;
readonly createdAt?: string; readonly createdAt?: string;
readonly id?: string; readonly id?: string;
@@ -122,6 +123,19 @@ v9 = {
"storageKey": "permission(action:\"core:state-of-applicability:delete\")" "storageKey": "permission(action:\"core:state-of-applicability:delete\")"
}, },
v10 = { v10 = {
"alias": "canExport",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:state-of-applicability:export"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:state-of-applicability:export\")"
},
v11 = {
"alias": null, "alias": null,
"args": null, "args": null,
"concreteType": "Organization", "concreteType": "Organization",
@@ -133,7 +147,7 @@ v10 = {
], ],
"storageKey": null "storageKey": null
}, },
v11 = { v12 = {
"alias": null, "alias": null,
"args": null, "args": null,
"concreteType": "People", "concreteType": "People",
@@ -180,6 +194,7 @@ return {
(v9/*: any*/), (v9/*: any*/),
(v10/*: any*/), (v10/*: any*/),
(v11/*: any*/), (v11/*: any*/),
(v12/*: any*/),
{ {
"args": null, "args": null,
"kind": "FragmentSpread", "kind": "FragmentSpread",
@@ -230,6 +245,7 @@ return {
(v9/*: any*/), (v9/*: any*/),
(v10/*: any*/), (v10/*: any*/),
(v11/*: any*/), (v11/*: any*/),
(v12/*: any*/),
{ {
"alias": "controlsInfo", "alias": "controlsInfo",
"args": [ "args": [
@@ -386,16 +402,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "6189c0ca41e3327629d744a658cbe6f1", "cacheID": "529e47a135ba24716b749998d1181d9a",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "StateOfApplicabilityGraphNodeQuery", "name": "StateOfApplicabilityGraphNodeQuery",
"operationKind": "query", "operationKind": "query",
"text": "query StateOfApplicabilityGraphNodeQuery(\n $stateOfApplicabilityId: ID!\n) {\n node(id: $stateOfApplicabilityId) {\n __typename\n ... on StateOfApplicability {\n id\n name\n sourceId\n snapshotId\n createdAt\n updatedAt\n canUpdate: permission(action: \"core:state-of-applicability:update\")\n canDelete: permission(action: \"core:state-of-applicability:delete\")\n organization {\n id\n }\n owner {\n id\n fullName\n }\n ...StateOfApplicabilityControlsTabFragment\n }\n id\n }\n}\n\nfragment StateOfApplicabilityControlsTabFragment on StateOfApplicability {\n id\n controlsInfo: controls(first: 0) {\n totalCount\n }\n canCreateStateOfApplicabilityControlMapping: permission(action: \"core:state-of-applicability-control-mapping:create\")\n canDeleteStateOfApplicabilityControlMapping: permission(action: \"core:state-of-applicability-control-mapping:delete\")\n availableControls {\n controlId\n sectionTitle\n name\n frameworkId\n frameworkName\n organizationId\n stateOfApplicabilityId\n applicability\n justification\n bestPractice\n regulatory\n contractual\n riskAssessment\n }\n}\n" "text": "query StateOfApplicabilityGraphNodeQuery(\n $stateOfApplicabilityId: ID!\n) {\n node(id: $stateOfApplicabilityId) {\n __typename\n ... on StateOfApplicability {\n id\n name\n sourceId\n snapshotId\n createdAt\n updatedAt\n canUpdate: permission(action: \"core:state-of-applicability:update\")\n canDelete: permission(action: \"core:state-of-applicability:delete\")\n canExport: permission(action: \"core:state-of-applicability:export\")\n organization {\n id\n }\n owner {\n id\n fullName\n }\n ...StateOfApplicabilityControlsTabFragment\n }\n id\n }\n}\n\nfragment StateOfApplicabilityControlsTabFragment on StateOfApplicability {\n id\n controlsInfo: controls(first: 0) {\n totalCount\n }\n canCreateStateOfApplicabilityControlMapping: permission(action: \"core:state-of-applicability-control-mapping:create\")\n canDeleteStateOfApplicabilityControlMapping: permission(action: \"core:state-of-applicability-control-mapping:delete\")\n availableControls {\n controlId\n sectionTitle\n name\n frameworkId\n frameworkName\n organizationId\n stateOfApplicabilityId\n applicability\n justification\n bestPractice\n regulatory\n contractual\n riskAssessment\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "1a1f3480b9c66597d146c48ed1b00bd1"; (node as any).hash = "5ea300f4dd399cafd7985072c2013dbe";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<c1333ad749a9c6aaf8f6f671bc578c04>> * @generated SignedSource<<63f008ba2b41fba49ba0c3145874aa72>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -76,12 +76,12 @@ const node: ReaderFragment = {
{ {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:measures:list" "value": "core:measure:list"
} }
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": "permission(action:\"core:measures:list\")" "storageKey": "permission(action:\"core:measure:list\")"
}, },
{ {
"alias": "canListRisks", "alias": "canListRisks",
@@ -102,12 +102,12 @@ const node: ReaderFragment = {
{ {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:frameworks:list" "value": "core:framework:list"
} }
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": "permission(action:\"core:frameworks:list\")" "storageKey": "permission(action:\"core:framework:list\")"
}, },
{ {
"alias": "canListPeople", "alias": "canListPeople",
@@ -309,6 +309,6 @@ const node: ReaderFragment = {
"abstractKey": null "abstractKey": null
}; };
(node as any).hash = "3a7071a1d9ed4ffc115f5bc22e283a50"; (node as any).hash = "6d640139997b2c7561699c1efb668650";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<d089cfdf5c585ba8fcb83456438efe62>> * @generated SignedSource<<0f5d74875ad984071d92f1d0e3cfce12>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -352,12 +352,12 @@ return {
{ {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:measures:list" "value": "core:measure:list"
} }
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": "permission(action:\"core:measures:list\")" "storageKey": "permission(action:\"core:measure:list\")"
}, },
{ {
"alias": "canListRisks", "alias": "canListRisks",
@@ -378,12 +378,12 @@ return {
{ {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:frameworks:list" "value": "core:framework:list"
} }
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": "permission(action:\"core:frameworks:list\")" "storageKey": "permission(action:\"core:framework:list\")"
}, },
{ {
"alias": "canListPeople", "alias": "canListPeople",
@@ -618,12 +618,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "d7e0b080876472d059c97bf57818c697", "cacheID": "f380d0fe83b6d660c61c379af4d2eb7d",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "ViewerMembershipLayoutQuery", "name": "ViewerMembershipLayoutQuery",
"operationKind": "query", "operationKind": "query",
"text": "query ViewerMembershipLayoutQuery(\n $organizationId: ID!\n $hideSidebar: Boolean!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...MembershipsDropdown_organizationFragment\n ...ViewerMembershipDropdownFragment\n ...SidebarFragment @skip(if: $hideSidebar)\n viewerMembership {\n role\n profile {\n fullName\n id\n }\n id\n }\n }\n id\n }\n viewer {\n email\n ...MembershipsDropdown_viewerFragment\n pendingInvitations {\n totalCount\n }\n id\n }\n}\n\nfragment MembershipsDropdown_organizationFragment on Organization {\n name\n}\n\nfragment MembershipsDropdown_viewerFragment on Identity {\n pendingInvitations {\n totalCount\n }\n}\n\nfragment SidebarFragment on Organization {\n canListMeetings: permission(action: \"core:meeting:list\")\n canListTasks: permission(action: \"core:task:list\")\n canListMeasures: permission(action: \"core:measures:list\")\n canListRisks: permission(action: \"core:risk:list\")\n canListFrameworks: permission(action: \"core:frameworks:list\")\n canListPeople: permission(action: \"core:people:list\")\n canListVendors: permission(action: \"core:vendor:list\")\n canListDocuments: permission(action: \"core:document:list\")\n canListAssets: permission(action: \"core:asset:list\")\n canListData: permission(action: \"core:datum:list\")\n canListAudits: permission(action: \"core:audit:list\")\n canListNonconformities: permission(action: \"core:nonconformity:list\")\n canListObligations: permission(action: \"core:obligation:list\")\n canListContinualImprovements: permission(action: \"core:continual-improvement:list\")\n canListProcessingActivities: permission(action: \"core:processing-activity:list\")\n canListRightsRequests: permission(action: \"core:rights-request:list\")\n canListSnapshots: permission(action: \"core:snapshot:list\")\n canGetTrustCenter: permission(action: \"core:trust-center:get\")\n canUpdateOrganization: permission(action: \"iam:organization:update\")\n canListStatesOfApplicability: permission(action: \"core:state-of-applicability:list\")\n}\n\nfragment ViewerMembershipDropdownFragment on Organization {\n viewerMembership {\n identity {\n email\n canListAPIKeys: permission(action: \"iam:personal-api-key:list\")\n id\n }\n profile {\n fullName\n id\n }\n id\n }\n}\n" "text": "query ViewerMembershipLayoutQuery(\n $organizationId: ID!\n $hideSidebar: Boolean!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...MembershipsDropdown_organizationFragment\n ...ViewerMembershipDropdownFragment\n ...SidebarFragment @skip(if: $hideSidebar)\n viewerMembership {\n role\n profile {\n fullName\n id\n }\n id\n }\n }\n id\n }\n viewer {\n email\n ...MembershipsDropdown_viewerFragment\n pendingInvitations {\n totalCount\n }\n id\n }\n}\n\nfragment MembershipsDropdown_organizationFragment on Organization {\n name\n}\n\nfragment MembershipsDropdown_viewerFragment on Identity {\n pendingInvitations {\n totalCount\n }\n}\n\nfragment SidebarFragment on Organization {\n canListMeetings: permission(action: \"core:meeting:list\")\n canListTasks: permission(action: \"core:task:list\")\n canListMeasures: permission(action: \"core:measure:list\")\n canListRisks: permission(action: \"core:risk:list\")\n canListFrameworks: permission(action: \"core:framework:list\")\n canListPeople: permission(action: \"core:people:list\")\n canListVendors: permission(action: \"core:vendor:list\")\n canListDocuments: permission(action: \"core:document:list\")\n canListAssets: permission(action: \"core:asset:list\")\n canListData: permission(action: \"core:datum:list\")\n canListAudits: permission(action: \"core:audit:list\")\n canListNonconformities: permission(action: \"core:nonconformity:list\")\n canListObligations: permission(action: \"core:obligation:list\")\n canListContinualImprovements: permission(action: \"core:continual-improvement:list\")\n canListProcessingActivities: permission(action: \"core:processing-activity:list\")\n canListRightsRequests: permission(action: \"core:rights-request:list\")\n canListSnapshots: permission(action: \"core:snapshot:list\")\n canGetTrustCenter: permission(action: \"core:trust-center:get\")\n canUpdateOrganization: permission(action: \"iam:organization:update\")\n canListStatesOfApplicability: permission(action: \"core:state-of-applicability:list\")\n}\n\nfragment ViewerMembershipDropdownFragment on Organization {\n viewerMembership {\n identity {\n email\n canListAPIKeys: permission(action: \"iam:personal-api-key:list\")\n id\n }\n profile {\n fullName\n id\n }\n id\n }\n}\n"
} }
}; };
})(); })();

View File

@@ -204,6 +204,7 @@ export const stateOfApplicabilityNodeQuery = graphql`
updatedAt updatedAt
canUpdate: permission(action: "core:state-of-applicability:update") canUpdate: permission(action: "core:state-of-applicability:update")
canDelete: permission(action: "core:state-of-applicability:delete") canDelete: permission(action: "core:state-of-applicability:delete")
canExport: permission(action: "core:state-of-applicability:export")
organization { organization {
id id
} }

View File

@@ -31,9 +31,9 @@ const fragment = graphql`
fragment SidebarFragment on Organization { fragment SidebarFragment on Organization {
canListMeetings: permission(action: "core:meeting:list") canListMeetings: permission(action: "core:meeting:list")
canListTasks: permission(action: "core:task:list") canListTasks: permission(action: "core:task:list")
canListMeasures: permission(action: "core:measures:list") canListMeasures: permission(action: "core:measure:list")
canListRisks: permission(action: "core:risk:list") canListRisks: permission(action: "core:risk:list")
canListFrameworks: permission(action: "core:frameworks:list") canListFrameworks: permission(action: "core:framework:list")
canListPeople: permission(action: "core:people:list") canListPeople: permission(action: "core:people:list")
canListVendors: permission(action: "core:vendor:list") canListVendors: permission(action: "core:vendor:list")
canListDocuments: permission(action: "core:document:list") canListDocuments: permission(action: "core:document:list")

View File

@@ -43,6 +43,7 @@ const frameworkDetailFragment = graphql`
canUpdate: permission(action: "core:framework:update") canUpdate: permission(action: "core:framework:update")
canDelete: permission(action: "core:framework:delete") canDelete: permission(action: "core:framework:delete")
canCreateControl: permission(action: "core:control:create") canCreateControl: permission(action: "core:control:create")
canGenerateSOA: permission(action: "core:framework:generate-state-of-applicability")
organization { organization {
name name
} }
@@ -151,6 +152,8 @@ export default function FrameworkDetailPage(props: Props) {
); );
} }
const hasAnyAction = framework.canExport || framework.canDelete || framework.canGenerateSOA
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<PageHeader <PageHeader
@@ -171,53 +174,59 @@ export default function FrameworkDetailPage(props: Props) {
</Button> </Button>
</FrameworkFormDialog> </FrameworkFormDialog>
)} )}
<ActionDropdown variant="secondary"> {hasAnyAction && (
<DropdownItem <ActionDropdown variant="secondary">
variant="primary" {framework.canGenerateSOA &&
onClick={() => { <DropdownItem
generateFrameworkStateOfApplicability({ variant="primary"
variables: { frameworkId: framework.id }, onClick={() => {
onCompleted: (data) => { generateFrameworkStateOfApplicability({
if ( variables: { frameworkId: framework.id },
data onCompleted: (data) => {
.generateFrameworkStateOfApplicability if (
?.data data
) { .generateFrameworkStateOfApplicability
const link = ?.data
window.document.createElement("a"); ) {
link.href = const link =
data.generateFrameworkStateOfApplicability.data; window.document.createElement("a");
link.download = `${framework.organization.name}-${framework.name}-SOA.xlsx`; link.href =
window.document.body.appendChild(link); data.generateFrameworkStateOfApplicability.data;
link.click(); link.download = `${framework.organization.name}-${framework.name}-SOA.xlsx`;
window.document.body.removeChild(link); window.document.body.appendChild(link);
} link.click();
}, window.document.body.removeChild(link);
}); }
}} },
> });
{__("Download SOA")} }}
</DropdownItem> >
<DropdownItem {__("Download SOA")}
variant="primary" </DropdownItem>
onClick={() => { }
exportFramework({ {framework.canExport &&
variables: { frameworkId: framework.id }, <DropdownItem
}); variant="primary"
}} onClick={() => {
> exportFramework({
{__("Export Framework")} variables: { frameworkId: framework.id },
</DropdownItem> });
{framework.canDelete && ( }}
<DropdownItem >
icon={IconTrashCan} {__("Export Framework")}
variant="danger" </DropdownItem>
onClick={onDelete} }
> {framework.canDelete && (
{__("Delete")} <DropdownItem
</DropdownItem> icon={IconTrashCan}
)} variant="danger"
</ActionDropdown> onClick={onDelete}
>
{__("Delete")}
</DropdownItem>
)}
</ActionDropdown>
)}
</PageHeader> </PageHeader>
<div className="text-lg font-semibold"> <div className="text-lg font-semibold">
{__("Requirement categories")} {__("Requirement categories")}

View File

@@ -279,14 +279,16 @@ export default function StateOfApplicabilityDetailPage(props: Props) {
) )
} }
> >
<Button {stateOfApplicability.canExport && (
variant="secondary" <Button
icon={IconArrowDown} variant="secondary"
onClick={handleExport} icon={IconArrowDown}
disabled={isExporting} onClick={handleExport}
> disabled={isExporting}
{__("Export")} >
</Button> {__("Export")}
</Button>
)}
{canDelete && ( {canDelete && (
<ActionDropdown variant="secondary"> <ActionDropdown variant="secondary">
<DropdownItem <DropdownItem

View File

@@ -264,7 +264,8 @@ SET
name = @name, name = @name,
owner_id = @owner_id, owner_id = @owner_id,
updated_at = @updated_at updated_at = @updated_at
WHERE %s WHERE
%s
AND id = @state_of_applicability_id AND id = @state_of_applicability_id
` `
@@ -303,7 +304,8 @@ func (s *StateOfApplicability) Delete(
) error { ) error {
q := ` q := `
DELETE FROM states_of_applicability DELETE FROM states_of_applicability
WHERE %s WHERE
%s
AND id = @state_of_applicability_id AND id = @state_of_applicability_id
` `
q = fmt.Sprintf(q, scope.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment())
@@ -346,31 +348,32 @@ func (soas StatesOfApplicability) insertStateOfApplicabilitySnapshots(
) error { ) error {
query := ` query := `
INSERT INTO states_of_applicability ( INSERT INTO states_of_applicability (
id, id,
tenant_id, tenant_id,
organization_id, organization_id,
name, name,
source_id, source_id,
snapshot_id, snapshot_id,
owner_id, owner_id,
created_at, created_at,
updated_at updated_at
) )
SELECT SELECT
generate_gid(decode_base64_unpadded(@tenant_id), @state_of_applicability_entity_type), generate_gid(decode_base64_unpadded(@tenant_id), @state_of_applicability_entity_type),
@tenant_id, @tenant_id,
soa.organization_id, soa.organization_id,
soa.name, soa.name,
soa.id, soa.id,
@snapshot_id, @snapshot_id,
soa.owner_id, soa.owner_id,
soa.created_at, soa.created_at,
soa.updated_at soa.updated_at
FROM states_of_applicability soa FROM states_of_applicability soa
WHERE %s WHERE
AND soa.organization_id = @organization_id %s
AND soa.snapshot_id IS NULL AND soa.organization_id = @organization_id
` AND soa.snapshot_id IS NULL
`
query = fmt.Sprintf(query, scope.SQLFragment()) query = fmt.Sprintf(query, scope.SQLFragment())
@@ -399,47 +402,48 @@ func (soas StatesOfApplicability) insertStateOfApplicabilityControlSnapshots(
) error { ) error {
query := ` query := `
WITH source_soa AS ( WITH source_soa AS (
SELECT id, organization_id SELECT id, organization_id
FROM states_of_applicability FROM states_of_applicability
WHERE %s WHERE
AND organization_id = @organization_id %s
AND snapshot_id IS NULL AND organization_id = @organization_id
AND snapshot_id IS NULL
), ),
snapshot_soa AS ( snapshot_soa AS (
SELECT id, source_id SELECT id, source_id
FROM states_of_applicability FROM states_of_applicability
WHERE snapshot_id = @snapshot_id WHERE snapshot_id = @snapshot_id
) )
INSERT INTO states_of_applicability_controls ( INSERT INTO states_of_applicability_controls (
id, id,
state_of_applicability_id, state_of_applicability_id,
control_id, control_id,
organization_id, organization_id,
tenant_id, tenant_id,
snapshot_id, snapshot_id,
applicability, applicability,
justification, justification,
created_at, created_at,
updated_at updated_at
) )
SELECT SELECT
generate_gid(decode_base64_unpadded(@tenant_id), @state_of_applicability_control_entity_type), generate_gid(decode_base64_unpadded(@tenant_id), @state_of_applicability_control_entity_type),
snapshot_soa.id, snapshot_soa.id,
soac.control_id, soac.control_id,
soac.organization_id, soac.organization_id,
@tenant_id, @tenant_id,
@snapshot_id, @snapshot_id,
soac.applicability, soac.applicability,
soac.justification, soac.justification,
soac.created_at, soac.created_at,
soac.updated_at soac.updated_at
FROM states_of_applicability_controls soac FROM states_of_applicability_controls soac
INNER JOIN source_soa INNER JOIN source_soa
ON soac.state_of_applicability_id = source_soa.id ON soac.state_of_applicability_id = source_soa.id
INNER JOIN snapshot_soa INNER JOIN snapshot_soa
ON snapshot_soa.source_id = source_soa.id ON snapshot_soa.source_id = source_soa.id
WHERE soac.snapshot_id IS NULL WHERE soac.snapshot_id IS NULL
` `
query = fmt.Sprintf(query, scope.SQLFragment()) query = fmt.Sprintf(query, scope.SQLFragment())

View File

@@ -96,28 +96,29 @@ func (sac *StateOfApplicabilityControl) LoadByStateOfApplicabilityIDAndControlID
) error { ) error {
q := ` q := `
WITH current_soa AS ( WITH current_soa AS (
SELECT id SELECT id
FROM states_of_applicability FROM states_of_applicability
WHERE %s WHERE
AND id = @state_of_applicability_id %s
AND snapshot_id IS NULL AND id = @state_of_applicability_id
AND snapshot_id IS NULL
) )
SELECT SELECT
soac.id, soac.id,
soac.state_of_applicability_id, soac.state_of_applicability_id,
soac.control_id, soac.control_id,
soac.organization_id, soac.organization_id,
soac.snapshot_id, soac.snapshot_id,
soac.applicability, soac.applicability,
soac.justification, soac.justification,
soac.created_at, soac.created_at,
soac.updated_at soac.updated_at
FROM FROM
states_of_applicability_controls soac states_of_applicability_controls soac
INNER JOIN INNER JOIN
current_soa ON soac.state_of_applicability_id = current_soa.id current_soa ON soac.state_of_applicability_id = current_soa.id
WHERE WHERE
soac.control_id = @control_id soac.control_id = @control_id
LIMIT 1; LIMIT 1;
` `
q = fmt.Sprintf(q, scope.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment())
@@ -217,7 +218,8 @@ SET
applicability = @applicability, applicability = @applicability,
justification = @justification, justification = @justification,
updated_at = @updated_at updated_at = @updated_at
WHERE %s WHERE
%s
AND state_of_applicability_id = @state_of_applicability_id AND state_of_applicability_id = @state_of_applicability_id
AND control_id = @control_id AND control_id = @control_id
` `
@@ -304,15 +306,16 @@ func (sac *StateOfApplicabilityControl) Delete(
) error { ) error {
q := ` q := `
WITH current_soa AS ( WITH current_soa AS (
SELECT id SELECT id
FROM states_of_applicability FROM states_of_applicability
WHERE %s WHERE
AND id = @state_of_applicability_id %s
AND snapshot_id IS NULL AND id = @state_of_applicability_id
AND snapshot_id IS NULL
) )
DELETE FROM states_of_applicability_controls DELETE FROM states_of_applicability_controls
WHERE state_of_applicability_id IN (SELECT id FROM current_soa) WHERE state_of_applicability_id IN (SELECT id FROM current_soa)
AND control_id = @control_id; AND control_id = @control_id;
` `
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
@@ -380,39 +383,41 @@ func (sacs *StateOfApplicabilityControls) LoadByControlID(
) error { ) error {
q := ` q := `
WITH soac_ctrl AS ( WITH soac_ctrl AS (
SELECT SELECT
soac.id, soac.id,
soac.state_of_applicability_id, soac.state_of_applicability_id,
soac.control_id, soac.control_id,
soac.organization_id, soac.organization_id,
soac.snapshot_id, soac.snapshot_id,
soac.applicability, soac.applicability,
soac.justification, soac.justification,
soac.created_at, soac.created_at,
soac.updated_at, soac.updated_at,
soac.tenant_id soac.tenant_id
FROM FROM
states_of_applicability_controls soac states_of_applicability_controls soac
INNER JOIN INNER JOIN
states_of_applicability soa ON soac.state_of_applicability_id = soa.id states_of_applicability soa ON soac.state_of_applicability_id = soa.id
WHERE WHERE
soac.control_id = @control_id soac.%[1]s
AND soa.snapshot_id IS NULL AND soac.control_id = @control_id
AND soa.snapshot_id IS NULL
) )
SELECT SELECT
id, id,
state_of_applicability_id, state_of_applicability_id,
control_id, control_id,
organization_id, organization_id,
snapshot_id, snapshot_id,
applicability, applicability,
justification, justification,
created_at, created_at,
updated_at updated_at
FROM FROM
soac_ctrl soac_ctrl
WHERE %s WHERE
AND %s %[1]s
AND %[2]s
` `
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
@@ -442,21 +447,25 @@ func (sacs *StateOfApplicabilityControls) CountByControlID(
) (int, error) { ) (int, error) {
q := ` q := `
WITH soac_ctrl AS ( WITH soac_ctrl AS (
SELECT SELECT
soac.id, soac.id,
soac.organization_id, soac.organization_id,
soac.tenant_id soac.tenant_id
FROM FROM
states_of_applicability_controls soac states_of_applicability_controls soac
WHERE INNER JOIN
soac.control_id = @control_id states_of_applicability soa ON soac.state_of_applicability_id = soa.id
WHERE
soac.%[1]s
AND soac.control_id = @control_id
AND soa.snapshot_id IS NULL
) )
SELECT SELECT
COUNT(id) COUNT(id)
FROM FROM
soac_ctrl soac_ctrl
WHERE WHERE
%s; %[1]s;
` `
q = fmt.Sprintf(q, scope.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment())
@@ -499,7 +508,8 @@ filtered_controls AS (
c.tenant_id, c.tenant_id,
c.best_practice c.best_practice
FROM controls c FROM controls c
WHERE %s WHERE
%s
), ),
all_controls AS ( all_controls AS (
SELECT SELECT

View File

@@ -78,6 +78,8 @@ var ViewerPolicy = policy.NewPolicy(
ActionFileGet, ActionFileDownloadUrl, ActionFileGet, ActionFileDownloadUrl,
ActionSlackConnectionList, ActionSlackConnectionList,
ActionRightsRequestGet, ActionRightsRequestList, ActionRightsRequestGet, ActionRightsRequestList,
ActionStateOfApplicabilityGet, ActionStateOfApplicabilityList,
ActionStateOfApplicabilityControlMappingList,
).WithSID("entity-read-access").When(organizationCondition), ).WithSID("entity-read-access").When(organizationCondition),
policy.Allow( policy.Allow(