The source headers, LICENSE files, and license metadata had drifted apart. Align the entire project to MIT: - Convert every source-file header to the MIT text across all comment styles (Go, TS, TSX, JS, MJS, SQL, CSS, GraphQL, shell), including SPDX-License-Identifier tags - Set the root and cookie-banner LICENSE files to the MIT text with a "MIT License" title line - Switch the package.json license fields, Docker image label, and cookie-banner README to MIT - Update docs and the genmodels header generator accordingly - Normalize copyright lines to a single format (Copyright (c) <year(s)> Probo Inc <hello@probo.com>.): unify the hello@getprobo.com and hello@probo.inc emails to hello@probo.com and the comma-separated years to a hyphenated range Genuine third-party references are intentionally left untouched: the Lucide icon attributions (Lucide is ISC) and the trivy dependency license allowlist. Signed-off-by: Sacha Al Himdani <sacha@probo.com>
1939 lines
52 KiB
Go
1939 lines
52 KiB
Go
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
|
|
package riskmanagement
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"go.gearno.de/kit/pg"
|
|
"go.probo.inc/probo/pkg/coredata"
|
|
"go.probo.inc/probo/pkg/gid"
|
|
"go.probo.inc/probo/pkg/page"
|
|
"go.probo.inc/probo/pkg/validator"
|
|
)
|
|
|
|
const (
|
|
TitleMaxLength = 1000
|
|
ContentMaxLength = 5000
|
|
)
|
|
|
|
type Service struct {
|
|
pg *pg.Client
|
|
}
|
|
|
|
func NewService(pgClient *pg.Client) *Service {
|
|
return &Service{pg: pgClient}
|
|
}
|
|
|
|
type (
|
|
CreateRiskAssessmentRequest struct {
|
|
OrganizationID gid.GID
|
|
Name string
|
|
Description *string
|
|
}
|
|
|
|
UpdateRiskAssessmentRequest struct {
|
|
ID gid.GID
|
|
Name *string
|
|
Description **string
|
|
}
|
|
|
|
CreateRiskAssessmentScopeRequest struct {
|
|
RiskAssessmentID gid.GID
|
|
Name string
|
|
}
|
|
|
|
UpdateRiskAssessmentScopeRequest struct {
|
|
ID gid.GID
|
|
Name *string
|
|
}
|
|
|
|
CreateRiskAssessmentBoundaryRequest struct {
|
|
RiskAssessmentScopeID gid.GID
|
|
ParentBoundaryID *gid.GID
|
|
Name string
|
|
}
|
|
|
|
UpdateRiskAssessmentBoundaryRequest struct {
|
|
ID gid.GID
|
|
ParentBoundaryID **gid.GID
|
|
Name *string
|
|
}
|
|
|
|
CreateRiskAssessmentNodeRequest struct {
|
|
RiskAssessmentScopeID gid.GID
|
|
BoundaryID *gid.GID
|
|
NodeType coredata.RiskAssessmentNodeType
|
|
Name string
|
|
}
|
|
|
|
UpdateRiskAssessmentNodeRequest struct {
|
|
ID gid.GID
|
|
BoundaryID **gid.GID
|
|
NodeType *coredata.RiskAssessmentNodeType
|
|
Name *string
|
|
}
|
|
|
|
CreateRiskAssessmentProcessRequest struct {
|
|
RiskAssessmentScopeID gid.GID
|
|
SourceNodeID gid.GID
|
|
TargetNodeID gid.GID
|
|
Name string
|
|
}
|
|
|
|
UpdateRiskAssessmentProcessRequest struct {
|
|
ID gid.GID
|
|
SourceNodeID *gid.GID
|
|
TargetNodeID *gid.GID
|
|
Name *string
|
|
}
|
|
|
|
CreateRiskAssessmentThreatRequest struct {
|
|
RiskAssessmentScopeID gid.GID
|
|
ProcessID gid.GID
|
|
Name string
|
|
Category string
|
|
}
|
|
|
|
UpdateRiskAssessmentThreatRequest struct {
|
|
ID gid.GID
|
|
ProcessID *gid.GID
|
|
Name *string
|
|
Category *string
|
|
}
|
|
|
|
CreateRiskAssessmentScenarioRequest struct {
|
|
RiskAssessmentScopeID gid.GID
|
|
Name string
|
|
Description *string
|
|
}
|
|
|
|
UpdateRiskAssessmentScenarioRequest struct {
|
|
ID gid.GID
|
|
Name *string
|
|
Description **string
|
|
}
|
|
|
|
LinkRiskAssessmentScenarioThreatRequest struct {
|
|
RiskAssessmentScenarioID gid.GID
|
|
ThreatID gid.GID
|
|
}
|
|
|
|
UnlinkRiskAssessmentScenarioThreatRequest struct {
|
|
RiskAssessmentScenarioID gid.GID
|
|
ThreatID gid.GID
|
|
}
|
|
|
|
LinkRiskAssessmentScenarioRiskRequest struct {
|
|
RiskAssessmentScenarioID gid.GID
|
|
RiskID gid.GID
|
|
}
|
|
|
|
UnlinkRiskAssessmentScenarioRiskRequest struct {
|
|
RiskAssessmentScenarioID gid.GID
|
|
RiskID gid.GID
|
|
}
|
|
)
|
|
|
|
func (r *CreateRiskAssessmentRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
|
v.Check(r.Name, "name", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength))
|
|
v.Check(r.Description, "description", validator.SafeText(ContentMaxLength))
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *UpdateRiskAssessmentRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.ID, "id", validator.Required(), validator.GID(coredata.RiskAssessmentEntityType))
|
|
v.Check(r.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
|
|
v.Check(r.Description, "description", validator.SafeText(ContentMaxLength))
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *CreateRiskAssessmentScopeRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.RiskAssessmentID, "risk_assessment_id", validator.Required(), validator.GID(coredata.RiskAssessmentEntityType))
|
|
v.Check(r.Name, "name", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength))
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *UpdateRiskAssessmentScopeRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.ID, "id", validator.Required(), validator.GID(coredata.RiskAssessmentScopeEntityType))
|
|
v.Check(r.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *CreateRiskAssessmentBoundaryRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.RiskAssessmentScopeID, "risk_assessment_scope_id", validator.Required(), validator.GID(coredata.RiskAssessmentScopeEntityType))
|
|
v.Check(r.Name, "name", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength))
|
|
|
|
if r.ParentBoundaryID != nil {
|
|
v.Check(*r.ParentBoundaryID, "parent_boundary_id", validator.Required(), validator.GID(coredata.RiskAssessmentBoundaryEntityType))
|
|
}
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *UpdateRiskAssessmentBoundaryRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.ID, "id", validator.Required(), validator.GID(coredata.RiskAssessmentBoundaryEntityType))
|
|
v.Check(r.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
|
|
|
|
if r.ParentBoundaryID != nil && *r.ParentBoundaryID != nil {
|
|
v.Check(**r.ParentBoundaryID, "parent_boundary_id", validator.Required(), validator.GID(coredata.RiskAssessmentBoundaryEntityType))
|
|
}
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *CreateRiskAssessmentNodeRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.RiskAssessmentScopeID, "risk_assessment_scope_id", validator.Required(), validator.GID(coredata.RiskAssessmentScopeEntityType))
|
|
v.Check(r.Name, "name", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength))
|
|
v.Check(r.NodeType, "node_type", validator.Required(), validator.OneOfSlice(coredata.RiskAssessmentNodeTypes()))
|
|
|
|
if r.BoundaryID != nil {
|
|
v.Check(*r.BoundaryID, "boundary_id", validator.Required(), validator.GID(coredata.RiskAssessmentBoundaryEntityType))
|
|
}
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *UpdateRiskAssessmentNodeRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.ID, "id", validator.Required(), validator.GID(coredata.RiskAssessmentNodeEntityType))
|
|
v.Check(r.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
|
|
v.Check(r.NodeType, "node_type", validator.OneOfSlice(coredata.RiskAssessmentNodeTypes()))
|
|
|
|
if r.BoundaryID != nil && *r.BoundaryID != nil {
|
|
v.Check(**r.BoundaryID, "boundary_id", validator.Required(), validator.GID(coredata.RiskAssessmentBoundaryEntityType))
|
|
}
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *CreateRiskAssessmentProcessRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.RiskAssessmentScopeID, "risk_assessment_scope_id", validator.Required(), validator.GID(coredata.RiskAssessmentScopeEntityType))
|
|
v.Check(r.SourceNodeID, "source_node_id", validator.Required(), validator.GID(coredata.RiskAssessmentNodeEntityType))
|
|
v.Check(r.TargetNodeID, "target_node_id", validator.Required(), validator.GID(coredata.RiskAssessmentNodeEntityType))
|
|
v.Check(r.Name, "name", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength))
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *UpdateRiskAssessmentProcessRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.ID, "id", validator.Required(), validator.GID(coredata.RiskAssessmentProcessEntityType))
|
|
v.Check(r.SourceNodeID, "source_node_id", validator.GID(coredata.RiskAssessmentNodeEntityType))
|
|
v.Check(r.TargetNodeID, "target_node_id", validator.GID(coredata.RiskAssessmentNodeEntityType))
|
|
v.Check(r.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *CreateRiskAssessmentThreatRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.RiskAssessmentScopeID, "risk_assessment_scope_id", validator.Required(), validator.GID(coredata.RiskAssessmentScopeEntityType))
|
|
v.Check(r.ProcessID, "process_id", validator.Required(), validator.GID(coredata.RiskAssessmentProcessEntityType))
|
|
v.Check(r.Name, "name", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength))
|
|
v.Check(r.Category, "category", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength))
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *UpdateRiskAssessmentThreatRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.ID, "id", validator.Required(), validator.GID(coredata.RiskAssessmentThreatEntityType))
|
|
v.Check(r.ProcessID, "process_id", validator.GID(coredata.RiskAssessmentProcessEntityType))
|
|
v.Check(r.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
|
|
v.Check(r.Category, "category", validator.SafeTextNoNewLine(TitleMaxLength))
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *CreateRiskAssessmentScenarioRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.RiskAssessmentScopeID, "risk_assessment_scope_id", validator.Required(), validator.GID(coredata.RiskAssessmentScopeEntityType))
|
|
v.Check(r.Name, "name", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength))
|
|
v.Check(r.Description, "description", validator.SafeText(ContentMaxLength))
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *LinkRiskAssessmentScenarioThreatRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.RiskAssessmentScenarioID, "risk_scenario_id", validator.Required(), validator.GID(coredata.RiskAssessmentScenarioEntityType))
|
|
v.Check(r.ThreatID, "threat_id", validator.Required(), validator.GID(coredata.RiskAssessmentThreatEntityType))
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *UnlinkRiskAssessmentScenarioThreatRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.RiskAssessmentScenarioID, "risk_scenario_id", validator.Required(), validator.GID(coredata.RiskAssessmentScenarioEntityType))
|
|
v.Check(r.ThreatID, "threat_id", validator.Required(), validator.GID(coredata.RiskAssessmentThreatEntityType))
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *LinkRiskAssessmentScenarioRiskRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.RiskAssessmentScenarioID, "risk_scenario_id", validator.Required(), validator.GID(coredata.RiskAssessmentScenarioEntityType))
|
|
v.Check(r.RiskID, "risk_id", validator.Required(), validator.GID(coredata.RiskEntityType))
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *UnlinkRiskAssessmentScenarioRiskRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.RiskAssessmentScenarioID, "risk_scenario_id", validator.Required(), validator.GID(coredata.RiskAssessmentScenarioEntityType))
|
|
v.Check(r.RiskID, "risk_id", validator.Required(), validator.GID(coredata.RiskEntityType))
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (r *UpdateRiskAssessmentScenarioRequest) Validate() error {
|
|
v := validator.New()
|
|
v.Check(r.ID, "id", validator.Required(), validator.GID(coredata.RiskAssessmentScenarioEntityType))
|
|
v.Check(r.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
|
|
v.Check(r.Description, "description", validator.SafeText(ContentMaxLength))
|
|
|
|
return v.Error()
|
|
}
|
|
|
|
func (s *Service) Create(ctx context.Context, scope coredata.Scoper, req CreateRiskAssessmentRequest) (*coredata.RiskAssessment, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
now := time.Now()
|
|
ra := &coredata.RiskAssessment{
|
|
ID: gid.New(scope.GetTenantID(), coredata.RiskAssessmentEntityType),
|
|
OrganizationID: req.OrganizationID,
|
|
Name: req.Name,
|
|
Description: req.Description,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
err := s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
if err := ra.Insert(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot insert risk assessment: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ra, nil
|
|
}
|
|
|
|
func (s *Service) Get(ctx context.Context, scope coredata.Scoper, id gid.GID) (*coredata.RiskAssessment, error) {
|
|
ra := &coredata.RiskAssessment{}
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := ra.LoadByID(ctx, conn, scope, id); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ra, nil
|
|
}
|
|
|
|
func (s *Service) Update(ctx context.Context, scope coredata.Scoper, req UpdateRiskAssessmentRequest) (*coredata.RiskAssessment, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
ra := &coredata.RiskAssessment{}
|
|
|
|
err := s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
if err := ra.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment: %w", err)
|
|
}
|
|
|
|
if req.Name != nil {
|
|
ra.Name = *req.Name
|
|
}
|
|
|
|
if req.Description != nil {
|
|
ra.Description = *req.Description
|
|
}
|
|
|
|
ra.UpdatedAt = time.Now()
|
|
if err := ra.Update(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot update risk assessment: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ra, nil
|
|
}
|
|
|
|
func (s *Service) Delete(ctx context.Context, scope coredata.Scoper, id gid.GID) error {
|
|
return s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
ra := &coredata.RiskAssessment{}
|
|
if err := ra.Delete(ctx, tx, scope, id); err != nil {
|
|
return fmt.Errorf("cannot delete risk assessment: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
}
|
|
|
|
func (s *Service) ListForOrganizationID(
|
|
ctx context.Context,
|
|
scope coredata.Scoper,
|
|
organizationID gid.GID,
|
|
cursor *page.Cursor[coredata.RiskAssessmentOrderField],
|
|
) (*page.Page[*coredata.RiskAssessment, coredata.RiskAssessmentOrderField], error) {
|
|
var results coredata.RiskAssessments
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := results.LoadByOrganizationID(ctx, conn, scope, organizationID, cursor); err != nil {
|
|
return fmt.Errorf("cannot list risk assessments: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return page.NewPage(results, cursor), nil
|
|
}
|
|
|
|
func (s *Service) CountForOrganizationID(
|
|
ctx context.Context,
|
|
scope coredata.Scoper,
|
|
organizationID gid.GID,
|
|
) (int, error) {
|
|
var count int
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) (err error) {
|
|
ras := &coredata.RiskAssessments{}
|
|
|
|
count, err = ras.CountByOrganizationID(ctx, conn, scope, organizationID)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot count risk assessments: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *Service) CreateScope(ctx context.Context, scope coredata.Scoper, req CreateRiskAssessmentScopeRequest) (*coredata.RiskAssessmentScope, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
now := time.Now()
|
|
raScope := &coredata.RiskAssessmentScope{
|
|
ID: gid.New(scope.GetTenantID(), coredata.RiskAssessmentScopeEntityType),
|
|
RiskAssessmentID: req.RiskAssessmentID,
|
|
Name: req.Name,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
err := s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
ra := coredata.RiskAssessment{}
|
|
if err := ra.LoadByID(ctx, tx, scope, req.RiskAssessmentID); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment: %w", err)
|
|
}
|
|
|
|
raScope.OrganizationID = ra.OrganizationID
|
|
if err := raScope.Insert(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot insert risk assessment scope: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return raScope, nil
|
|
}
|
|
|
|
func (s *Service) GetScope(ctx context.Context, scope coredata.Scoper, id gid.GID) (*coredata.RiskAssessmentScope, error) {
|
|
raScope := &coredata.RiskAssessmentScope{}
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := raScope.LoadByID(ctx, conn, scope, id); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment scope: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return raScope, nil
|
|
}
|
|
|
|
func (s *Service) UpdateScope(ctx context.Context, scope coredata.Scoper, req UpdateRiskAssessmentScopeRequest) (*coredata.RiskAssessmentScope, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
raScope := &coredata.RiskAssessmentScope{}
|
|
|
|
err := s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
if err := raScope.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment scope: %w", err)
|
|
}
|
|
|
|
if req.Name != nil {
|
|
raScope.Name = *req.Name
|
|
}
|
|
|
|
raScope.UpdatedAt = time.Now()
|
|
if err := raScope.Update(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot update risk assessment scope: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return raScope, nil
|
|
}
|
|
|
|
func (s *Service) DeleteScope(ctx context.Context, scope coredata.Scoper, id gid.GID) error {
|
|
return s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
raScope := &coredata.RiskAssessmentScope{}
|
|
if err := raScope.Delete(ctx, tx, scope, id); err != nil {
|
|
return fmt.Errorf("cannot delete risk assessment scope: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
}
|
|
|
|
func (s *Service) ListScopesForRiskAssessmentID(
|
|
ctx context.Context,
|
|
scope coredata.Scoper,
|
|
riskAssessmentID gid.GID,
|
|
cursor *page.Cursor[coredata.RiskAssessmentScopeOrderField],
|
|
) (*page.Page[*coredata.RiskAssessmentScope, coredata.RiskAssessmentScopeOrderField], error) {
|
|
var results coredata.RiskAssessmentScopes
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := results.LoadByRiskAssessmentID(ctx, conn, scope, riskAssessmentID, cursor); err != nil {
|
|
return fmt.Errorf("cannot list risk assessment scopes: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return page.NewPage(results, cursor), nil
|
|
}
|
|
|
|
func (s *Service) CountScopesForRiskAssessmentID(ctx context.Context, scope coredata.Scoper, riskAssessmentID gid.GID) (int, error) {
|
|
var count int
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) (err error) {
|
|
ss := &coredata.RiskAssessmentScopes{}
|
|
|
|
count, err = ss.CountByRiskAssessmentID(ctx, conn, scope, riskAssessmentID)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot count risk assessment scopes: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *Service) CreateNode(ctx context.Context, scope coredata.Scoper, req CreateRiskAssessmentNodeRequest) (*coredata.RiskAssessmentNode, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
now := time.Now()
|
|
node := &coredata.RiskAssessmentNode{
|
|
ID: gid.New(scope.GetTenantID(), coredata.RiskAssessmentNodeEntityType),
|
|
RiskAssessmentScopeID: req.RiskAssessmentScopeID,
|
|
BoundaryID: req.BoundaryID,
|
|
NodeType: req.NodeType,
|
|
Name: req.Name,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
err := s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
raScope := coredata.RiskAssessmentScope{}
|
|
if err := raScope.LoadByID(ctx, tx, scope, req.RiskAssessmentScopeID); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment scope: %w", err)
|
|
}
|
|
|
|
if req.BoundaryID != nil {
|
|
if err := s.assertBoundaryInScope(ctx, tx, scope, *req.BoundaryID, req.RiskAssessmentScopeID, "boundary_id"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
node.OrganizationID = raScope.OrganizationID
|
|
if err := node.Insert(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot insert risk assessment node: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return node, nil
|
|
}
|
|
|
|
func (s *Service) GetNode(ctx context.Context, scope coredata.Scoper, id gid.GID) (*coredata.RiskAssessmentNode, error) {
|
|
node := &coredata.RiskAssessmentNode{}
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := node.LoadByID(ctx, conn, scope, id); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment node: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return node, nil
|
|
}
|
|
|
|
func (s *Service) UpdateNode(ctx context.Context, scope coredata.Scoper, req UpdateRiskAssessmentNodeRequest) (*coredata.RiskAssessmentNode, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
node := &coredata.RiskAssessmentNode{}
|
|
|
|
err := s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
if err := node.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment node: %w", err)
|
|
}
|
|
|
|
if req.Name != nil {
|
|
node.Name = *req.Name
|
|
}
|
|
|
|
if req.NodeType != nil {
|
|
node.NodeType = *req.NodeType
|
|
}
|
|
|
|
if req.BoundaryID != nil {
|
|
if *req.BoundaryID != nil {
|
|
if err := s.assertBoundaryInScope(ctx, tx, scope, **req.BoundaryID, node.RiskAssessmentScopeID, "boundary_id"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
node.BoundaryID = *req.BoundaryID
|
|
}
|
|
|
|
node.UpdatedAt = time.Now()
|
|
if err := node.Update(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot update risk assessment node: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return node, nil
|
|
}
|
|
|
|
func (s *Service) DeleteNode(ctx context.Context, scope coredata.Scoper, id gid.GID) error {
|
|
return s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
node := &coredata.RiskAssessmentNode{}
|
|
if err := node.Delete(ctx, tx, scope, id); err != nil {
|
|
return fmt.Errorf("cannot delete risk assessment node: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
}
|
|
|
|
func (s *Service) ListNodesForScopeID(
|
|
ctx context.Context,
|
|
scope coredata.Scoper,
|
|
scopeID gid.GID,
|
|
cursor *page.Cursor[coredata.RiskAssessmentNodeOrderField],
|
|
) (*page.Page[*coredata.RiskAssessmentNode, coredata.RiskAssessmentNodeOrderField], error) {
|
|
var results coredata.RiskAssessmentNodes
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := results.LoadByRiskAssessmentScopeID(ctx, conn, scope, scopeID, cursor); err != nil {
|
|
return fmt.Errorf("cannot list risk assessment nodes: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return page.NewPage(results, cursor), nil
|
|
}
|
|
|
|
func (s *Service) CountNodesForScopeID(ctx context.Context, scope coredata.Scoper, scopeID gid.GID) (int, error) {
|
|
var count int
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) (err error) {
|
|
ns := &coredata.RiskAssessmentNodes{}
|
|
|
|
count, err = ns.CountByRiskAssessmentScopeID(ctx, conn, scope, scopeID)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot count risk assessment nodes: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *Service) CreateBoundary(ctx context.Context, scope coredata.Scoper, req CreateRiskAssessmentBoundaryRequest) (*coredata.RiskAssessmentBoundary, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
now := time.Now()
|
|
boundary := &coredata.RiskAssessmentBoundary{
|
|
ID: gid.New(scope.GetTenantID(), coredata.RiskAssessmentBoundaryEntityType),
|
|
RiskAssessmentScopeID: req.RiskAssessmentScopeID,
|
|
ParentBoundaryID: req.ParentBoundaryID,
|
|
Name: req.Name,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
err := s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
raScope := coredata.RiskAssessmentScope{}
|
|
if err := raScope.LoadByID(ctx, tx, scope, req.RiskAssessmentScopeID); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment scope: %w", err)
|
|
}
|
|
|
|
if req.ParentBoundaryID != nil {
|
|
if err := s.assertBoundaryInScope(ctx, tx, scope, *req.ParentBoundaryID, req.RiskAssessmentScopeID, "parent_boundary_id"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
boundary.OrganizationID = raScope.OrganizationID
|
|
if err := boundary.Insert(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot insert risk assessment boundary: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return boundary, nil
|
|
}
|
|
|
|
func (s *Service) GetBoundary(ctx context.Context, scope coredata.Scoper, id gid.GID) (*coredata.RiskAssessmentBoundary, error) {
|
|
boundary := &coredata.RiskAssessmentBoundary{}
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := boundary.LoadByID(ctx, conn, scope, id); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment boundary: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return boundary, nil
|
|
}
|
|
|
|
func (s *Service) UpdateBoundary(ctx context.Context, scope coredata.Scoper, req UpdateRiskAssessmentBoundaryRequest) (*coredata.RiskAssessmentBoundary, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
boundary := &coredata.RiskAssessmentBoundary{}
|
|
|
|
err := s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
if err := boundary.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment boundary: %w", err)
|
|
}
|
|
|
|
if req.Name != nil {
|
|
boundary.Name = *req.Name
|
|
}
|
|
|
|
if req.ParentBoundaryID != nil {
|
|
if *req.ParentBoundaryID != nil {
|
|
if err := s.assertBoundaryInScope(ctx, tx, scope, **req.ParentBoundaryID, boundary.RiskAssessmentScopeID, "parent_boundary_id"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := s.assertNoBoundaryCycle(ctx, tx, scope, boundary.ID, **req.ParentBoundaryID, "parent_boundary_id"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
boundary.ParentBoundaryID = *req.ParentBoundaryID
|
|
}
|
|
|
|
boundary.UpdatedAt = time.Now()
|
|
if err := boundary.Update(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot update risk assessment boundary: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return boundary, nil
|
|
}
|
|
|
|
func (s *Service) DeleteBoundary(ctx context.Context, scope coredata.Scoper, id gid.GID) error {
|
|
return s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
boundary := &coredata.RiskAssessmentBoundary{}
|
|
if err := boundary.Delete(ctx, tx, scope, id); err != nil {
|
|
return fmt.Errorf("cannot delete risk assessment boundary: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
}
|
|
|
|
func (s *Service) ListBoundariesForScopeID(
|
|
ctx context.Context,
|
|
scope coredata.Scoper,
|
|
scopeID gid.GID,
|
|
cursor *page.Cursor[coredata.RiskAssessmentBoundaryOrderField],
|
|
) (*page.Page[*coredata.RiskAssessmentBoundary, coredata.RiskAssessmentBoundaryOrderField], error) {
|
|
var results coredata.RiskAssessmentBoundaries
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := results.LoadByRiskAssessmentScopeID(ctx, conn, scope, scopeID, cursor); err != nil {
|
|
return fmt.Errorf("cannot list risk assessment boundaries: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return page.NewPage(results, cursor), nil
|
|
}
|
|
|
|
func (s *Service) CountBoundariesForScopeID(ctx context.Context, scope coredata.Scoper, scopeID gid.GID) (int, error) {
|
|
var count int
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) (err error) {
|
|
bs := &coredata.RiskAssessmentBoundaries{}
|
|
|
|
count, err = bs.CountByRiskAssessmentScopeID(ctx, conn, scope, scopeID)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot count risk assessment boundaries: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *Service) CreateProcess(ctx context.Context, scope coredata.Scoper, req CreateRiskAssessmentProcessRequest) (*coredata.RiskAssessmentProcess, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
now := time.Now()
|
|
process := &coredata.RiskAssessmentProcess{
|
|
ID: gid.New(scope.GetTenantID(), coredata.RiskAssessmentProcessEntityType),
|
|
RiskAssessmentScopeID: req.RiskAssessmentScopeID,
|
|
SourceNodeID: req.SourceNodeID,
|
|
TargetNodeID: req.TargetNodeID,
|
|
Name: req.Name,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
err := s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
raScope := coredata.RiskAssessmentScope{}
|
|
if err := raScope.LoadByID(ctx, tx, scope, req.RiskAssessmentScopeID); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment scope: %w", err)
|
|
}
|
|
|
|
process.OrganizationID = raScope.OrganizationID
|
|
|
|
if err := s.assertNodeInScope(ctx, tx, scope, req.SourceNodeID, req.RiskAssessmentScopeID, "source_node_id"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := s.assertNodeInScope(ctx, tx, scope, req.TargetNodeID, req.RiskAssessmentScopeID, "target_node_id"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := process.Insert(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot insert risk assessment process: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return process, nil
|
|
}
|
|
|
|
func (s *Service) GetProcess(ctx context.Context, scope coredata.Scoper, id gid.GID) (*coredata.RiskAssessmentProcess, error) {
|
|
process := &coredata.RiskAssessmentProcess{}
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := process.LoadByID(ctx, conn, scope, id); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment process: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return process, nil
|
|
}
|
|
|
|
func (s *Service) UpdateProcess(ctx context.Context, scope coredata.Scoper, req UpdateRiskAssessmentProcessRequest) (*coredata.RiskAssessmentProcess, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
process := &coredata.RiskAssessmentProcess{}
|
|
|
|
err := s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
if err := process.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment process: %w", err)
|
|
}
|
|
|
|
if req.SourceNodeID != nil {
|
|
if err := s.assertNodeInScope(ctx, tx, scope, *req.SourceNodeID, process.RiskAssessmentScopeID, "source_node_id"); err != nil {
|
|
return err
|
|
}
|
|
|
|
process.SourceNodeID = *req.SourceNodeID
|
|
}
|
|
|
|
if req.TargetNodeID != nil {
|
|
if err := s.assertNodeInScope(ctx, tx, scope, *req.TargetNodeID, process.RiskAssessmentScopeID, "target_node_id"); err != nil {
|
|
return err
|
|
}
|
|
|
|
process.TargetNodeID = *req.TargetNodeID
|
|
}
|
|
|
|
if req.Name != nil {
|
|
process.Name = *req.Name
|
|
}
|
|
|
|
process.UpdatedAt = time.Now()
|
|
if err := process.Update(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot update risk assessment process: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return process, nil
|
|
}
|
|
|
|
func (s *Service) DeleteProcess(ctx context.Context, scope coredata.Scoper, id gid.GID) error {
|
|
return s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
process := &coredata.RiskAssessmentProcess{}
|
|
if err := process.Delete(ctx, tx, scope, id); err != nil {
|
|
return fmt.Errorf("cannot delete risk assessment process: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
}
|
|
|
|
func (s *Service) ListProcessesForScopeID(
|
|
ctx context.Context,
|
|
scope coredata.Scoper,
|
|
scopeID gid.GID,
|
|
cursor *page.Cursor[coredata.RiskAssessmentProcessOrderField],
|
|
) (*page.Page[*coredata.RiskAssessmentProcess, coredata.RiskAssessmentProcessOrderField], error) {
|
|
var results coredata.RiskAssessmentProcesses
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := results.LoadByRiskAssessmentScopeID(ctx, conn, scope, scopeID, cursor); err != nil {
|
|
return fmt.Errorf("cannot list risk assessment processes: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return page.NewPage(results, cursor), nil
|
|
}
|
|
|
|
func (s *Service) CountProcessesForScopeID(ctx context.Context, scope coredata.Scoper, scopeID gid.GID) (int, error) {
|
|
var count int
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) (err error) {
|
|
ps := &coredata.RiskAssessmentProcesses{}
|
|
|
|
count, err = ps.CountByRiskAssessmentScopeID(ctx, conn, scope, scopeID)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot count risk assessment processes: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *Service) CreateThreat(ctx context.Context, scope coredata.Scoper, req CreateRiskAssessmentThreatRequest) (*coredata.RiskAssessmentThreat, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
now := time.Now()
|
|
threat := &coredata.RiskAssessmentThreat{
|
|
ID: gid.New(scope.GetTenantID(), coredata.RiskAssessmentThreatEntityType),
|
|
RiskAssessmentScopeID: req.RiskAssessmentScopeID,
|
|
ProcessID: req.ProcessID,
|
|
Name: req.Name,
|
|
Category: req.Category,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
err := s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
raScope := coredata.RiskAssessmentScope{}
|
|
if err := raScope.LoadByID(ctx, tx, scope, req.RiskAssessmentScopeID); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment scope: %w", err)
|
|
}
|
|
|
|
threat.OrganizationID = raScope.OrganizationID
|
|
|
|
if err := s.assertProcessInScope(ctx, tx, scope, req.ProcessID, req.RiskAssessmentScopeID, "process_id"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := threat.Insert(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot insert risk threat: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return threat, nil
|
|
}
|
|
|
|
func (s *Service) GetThreat(ctx context.Context, scope coredata.Scoper, id gid.GID) (*coredata.RiskAssessmentThreat, error) {
|
|
threat := &coredata.RiskAssessmentThreat{}
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := threat.LoadByID(ctx, conn, scope, id); err != nil {
|
|
return fmt.Errorf("cannot load risk threat: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return threat, nil
|
|
}
|
|
|
|
func (s *Service) UpdateThreat(ctx context.Context, scope coredata.Scoper, req UpdateRiskAssessmentThreatRequest) (*coredata.RiskAssessmentThreat, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
threat := &coredata.RiskAssessmentThreat{}
|
|
|
|
err := s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
if err := threat.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
|
return fmt.Errorf("cannot load risk threat: %w", err)
|
|
}
|
|
|
|
if req.ProcessID != nil {
|
|
if err := s.assertProcessInScope(ctx, tx, scope, *req.ProcessID, threat.RiskAssessmentScopeID, "process_id"); err != nil {
|
|
return err
|
|
}
|
|
|
|
threat.ProcessID = *req.ProcessID
|
|
}
|
|
|
|
if req.Name != nil {
|
|
threat.Name = *req.Name
|
|
}
|
|
|
|
if req.Category != nil {
|
|
threat.Category = *req.Category
|
|
}
|
|
|
|
threat.UpdatedAt = time.Now()
|
|
if err := threat.Update(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot update risk threat: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return threat, nil
|
|
}
|
|
|
|
func (s *Service) DeleteThreat(ctx context.Context, scope coredata.Scoper, id gid.GID) error {
|
|
return s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
threat := &coredata.RiskAssessmentThreat{}
|
|
if err := threat.Delete(ctx, tx, scope, id); err != nil {
|
|
return fmt.Errorf("cannot delete risk threat: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
}
|
|
|
|
func (s *Service) ListThreatsForScopeID(
|
|
ctx context.Context,
|
|
scope coredata.Scoper,
|
|
scopeID gid.GID,
|
|
cursor *page.Cursor[coredata.RiskAssessmentThreatOrderField],
|
|
) (*page.Page[*coredata.RiskAssessmentThreat, coredata.RiskAssessmentThreatOrderField], error) {
|
|
var results coredata.RiskAssessmentThreats
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := results.LoadByRiskAssessmentScopeID(ctx, conn, scope, scopeID, cursor); err != nil {
|
|
return fmt.Errorf("cannot list risk threats: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return page.NewPage(results, cursor), nil
|
|
}
|
|
|
|
func (s *Service) CountThreatsForScopeID(ctx context.Context, scope coredata.Scoper, scopeID gid.GID) (int, error) {
|
|
var count int
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) (err error) {
|
|
ts := &coredata.RiskAssessmentThreats{}
|
|
|
|
count, err = ts.CountByRiskAssessmentScopeID(ctx, conn, scope, scopeID)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot count risk threats: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *Service) CreateScenario(ctx context.Context, scope coredata.Scoper, req CreateRiskAssessmentScenarioRequest) (*coredata.RiskAssessmentScenario, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
now := time.Now()
|
|
scenario := &coredata.RiskAssessmentScenario{
|
|
ID: gid.New(scope.GetTenantID(), coredata.RiskAssessmentScenarioEntityType),
|
|
RiskAssessmentScopeID: req.RiskAssessmentScopeID,
|
|
Name: req.Name,
|
|
Description: req.Description,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
err := s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
raScope := coredata.RiskAssessmentScope{}
|
|
if err := raScope.LoadByID(ctx, tx, scope, req.RiskAssessmentScopeID); err != nil {
|
|
return fmt.Errorf("cannot load risk assessment scope: %w", err)
|
|
}
|
|
|
|
scenario.OrganizationID = raScope.OrganizationID
|
|
if err := scenario.Insert(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot insert risk scenario: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return scenario, nil
|
|
}
|
|
|
|
func (s *Service) GetScenario(ctx context.Context, scope coredata.Scoper, id gid.GID) (*coredata.RiskAssessmentScenario, error) {
|
|
scenario := &coredata.RiskAssessmentScenario{}
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := scenario.LoadByID(ctx, conn, scope, id); err != nil {
|
|
return fmt.Errorf("cannot load risk scenario: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return scenario, nil
|
|
}
|
|
|
|
func (s *Service) UpdateScenario(ctx context.Context, scope coredata.Scoper, req UpdateRiskAssessmentScenarioRequest) (*coredata.RiskAssessmentScenario, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
scenario := &coredata.RiskAssessmentScenario{}
|
|
|
|
err := s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
if err := scenario.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
|
return fmt.Errorf("cannot load risk scenario: %w", err)
|
|
}
|
|
|
|
if req.Name != nil {
|
|
scenario.Name = *req.Name
|
|
}
|
|
|
|
if req.Description != nil {
|
|
scenario.Description = *req.Description
|
|
}
|
|
|
|
scenario.UpdatedAt = time.Now()
|
|
if err := scenario.Update(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot update risk scenario: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return scenario, nil
|
|
}
|
|
|
|
func (s *Service) DeleteScenario(ctx context.Context, scope coredata.Scoper, id gid.GID) error {
|
|
return s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
scenario := &coredata.RiskAssessmentScenario{}
|
|
if err := scenario.Delete(ctx, tx, scope, id); err != nil {
|
|
return fmt.Errorf("cannot delete risk scenario: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
}
|
|
|
|
func (s *Service) ListScenariosForOrganizationID(
|
|
ctx context.Context,
|
|
scope coredata.Scoper,
|
|
organizationID gid.GID,
|
|
cursor *page.Cursor[coredata.RiskAssessmentScenarioOrderField],
|
|
) (*page.Page[*coredata.RiskAssessmentScenario, coredata.RiskAssessmentScenarioOrderField], error) {
|
|
var results coredata.RiskAssessmentScenarios
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := results.LoadByOrganizationID(ctx, conn, scope, organizationID, cursor); err != nil {
|
|
return fmt.Errorf("cannot list risk scenarios: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return page.NewPage(results, cursor), nil
|
|
}
|
|
|
|
func (s *Service) CountScenariosForOrganizationID(
|
|
ctx context.Context,
|
|
scope coredata.Scoper,
|
|
organizationID gid.GID,
|
|
) (int, error) {
|
|
var count int
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) (err error) {
|
|
ss := &coredata.RiskAssessmentScenarios{}
|
|
|
|
count, err = ss.CountByOrganizationID(ctx, conn, scope, organizationID)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot count risk scenarios: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *Service) ListScenariosForRiskID(
|
|
ctx context.Context,
|
|
scope coredata.Scoper,
|
|
riskID gid.GID,
|
|
cursor *page.Cursor[coredata.RiskAssessmentScenarioOrderField],
|
|
) (*page.Page[*coredata.RiskAssessmentScenario, coredata.RiskAssessmentScenarioOrderField], error) {
|
|
var results coredata.RiskAssessmentScenarios
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := results.LoadByRiskID(ctx, conn, scope, riskID, cursor); err != nil {
|
|
return fmt.Errorf("cannot list risk scenarios: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return page.NewPage(results, cursor), nil
|
|
}
|
|
|
|
func (s *Service) CountScenariosForRiskID(ctx context.Context, scope coredata.Scoper, riskID gid.GID) (int, error) {
|
|
var count int
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) (err error) {
|
|
ss := &coredata.RiskAssessmentScenarios{}
|
|
|
|
count, err = ss.CountByRiskID(ctx, conn, scope, riskID)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot count risk scenarios: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *Service) ListScenariosForScopeID(
|
|
ctx context.Context,
|
|
scope coredata.Scoper,
|
|
scopeID gid.GID,
|
|
cursor *page.Cursor[coredata.RiskAssessmentScenarioOrderField],
|
|
) (*page.Page[*coredata.RiskAssessmentScenario, coredata.RiskAssessmentScenarioOrderField], error) {
|
|
var results coredata.RiskAssessmentScenarios
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := results.LoadByRiskAssessmentScopeID(ctx, conn, scope, scopeID, cursor); err != nil {
|
|
return fmt.Errorf("cannot list risk scenarios: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return page.NewPage(results, cursor), nil
|
|
}
|
|
|
|
func (s *Service) CountScenariosForScopeID(ctx context.Context, scope coredata.Scoper, scopeID gid.GID) (int, error) {
|
|
var count int
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) (err error) {
|
|
ss := &coredata.RiskAssessmentScenarios{}
|
|
|
|
count, err = ss.CountByRiskAssessmentScopeID(ctx, conn, scope, scopeID)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot count risk scenarios: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *Service) LinkScenarioThreat(ctx context.Context, scope coredata.Scoper, req LinkRiskAssessmentScenarioThreatRequest) error {
|
|
if err := req.Validate(); err != nil {
|
|
return fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
return s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
scenario := coredata.RiskAssessmentScenario{}
|
|
if err := scenario.LoadByID(ctx, tx, scope, req.RiskAssessmentScenarioID); err != nil {
|
|
return fmt.Errorf("cannot load risk scenario: %w", err)
|
|
}
|
|
|
|
threat := coredata.RiskAssessmentThreat{}
|
|
if err := threat.LoadByID(ctx, tx, scope, req.ThreatID); err != nil {
|
|
return fmt.Errorf("cannot load threat: %w", err)
|
|
}
|
|
|
|
if scenario.OrganizationID != threat.OrganizationID {
|
|
return validator.ValidationErrors{{
|
|
Field: "threat_id",
|
|
Code: validator.ErrorCodeCustom,
|
|
Message: "threat and scenario must belong to the same organization",
|
|
}}
|
|
}
|
|
|
|
link := &coredata.RiskAssessmentScenarioThreat{
|
|
RiskAssessmentScenarioID: req.RiskAssessmentScenarioID,
|
|
RiskAssessmentThreatID: req.ThreatID,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
if err := link.Insert(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot link scenario threat: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
}
|
|
|
|
func (s *Service) UnlinkScenarioThreat(ctx context.Context, scope coredata.Scoper, req UnlinkRiskAssessmentScenarioThreatRequest) error {
|
|
if err := req.Validate(); err != nil {
|
|
return fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
return s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
link := &coredata.RiskAssessmentScenarioThreat{
|
|
RiskAssessmentScenarioID: req.RiskAssessmentScenarioID,
|
|
RiskAssessmentThreatID: req.ThreatID,
|
|
}
|
|
if err := link.Delete(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot unlink scenario threat: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
}
|
|
|
|
func (s *Service) LinkScenarioRisk(ctx context.Context, scope coredata.Scoper, req LinkRiskAssessmentScenarioRiskRequest) error {
|
|
if err := req.Validate(); err != nil {
|
|
return fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
return s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
scenario := coredata.RiskAssessmentScenario{}
|
|
if err := scenario.LoadByID(ctx, tx, scope, req.RiskAssessmentScenarioID); err != nil {
|
|
return fmt.Errorf("cannot load risk scenario: %w", err)
|
|
}
|
|
|
|
risk := coredata.Risk{}
|
|
if err := risk.LoadByID(ctx, tx, scope, req.RiskID); err != nil {
|
|
return fmt.Errorf("cannot load risk: %w", err)
|
|
}
|
|
|
|
if scenario.OrganizationID != risk.OrganizationID {
|
|
return validator.ValidationErrors{{
|
|
Field: "risk_id",
|
|
Code: validator.ErrorCodeCustom,
|
|
Message: "risk and scenario must belong to the same organization",
|
|
}}
|
|
}
|
|
|
|
link := &coredata.RiskAssessmentScenarioRisk{
|
|
RiskAssessmentScenarioID: req.RiskAssessmentScenarioID,
|
|
RiskID: req.RiskID,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
if err := link.Insert(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot link scenario risk: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
}
|
|
|
|
func (s *Service) UnlinkScenarioRisk(ctx context.Context, scope coredata.Scoper, req UnlinkRiskAssessmentScenarioRiskRequest) error {
|
|
if err := req.Validate(); err != nil {
|
|
return fmt.Errorf("invalid request: %w", err)
|
|
}
|
|
|
|
return s.pg.WithTx(
|
|
ctx,
|
|
func(ctx context.Context, tx pg.Tx) error {
|
|
link := &coredata.RiskAssessmentScenarioRisk{
|
|
RiskAssessmentScenarioID: req.RiskAssessmentScenarioID,
|
|
RiskID: req.RiskID,
|
|
}
|
|
if err := link.Delete(ctx, tx, scope); err != nil {
|
|
return fmt.Errorf("cannot unlink scenario risk: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
}
|
|
|
|
func (s *Service) ListThreatsForScenarioID(
|
|
ctx context.Context,
|
|
scope coredata.Scoper,
|
|
scenarioID gid.GID,
|
|
cursor *page.Cursor[coredata.RiskAssessmentThreatOrderField],
|
|
) (*page.Page[*coredata.RiskAssessmentThreat, coredata.RiskAssessmentThreatOrderField], error) {
|
|
var results coredata.RiskAssessmentThreats
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := results.LoadByScenarioID(ctx, conn, scope, scenarioID, cursor); err != nil {
|
|
return fmt.Errorf("cannot list scenario threats: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return page.NewPage(results, cursor), nil
|
|
}
|
|
|
|
func (s *Service) CountThreatsForScenarioID(ctx context.Context, scope coredata.Scoper, scenarioID gid.GID) (int, error) {
|
|
var count int
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) (err error) {
|
|
ts := &coredata.RiskAssessmentThreats{}
|
|
|
|
count, err = ts.CountByScenarioID(ctx, conn, scope, scenarioID)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot count scenario threats: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *Service) ListRisksForScenarioID(
|
|
ctx context.Context,
|
|
scope coredata.Scoper,
|
|
scenarioID gid.GID,
|
|
cursor *page.Cursor[coredata.RiskOrderField],
|
|
) (*page.Page[*coredata.Risk, coredata.RiskOrderField], error) {
|
|
var results coredata.Risks
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) error {
|
|
if err := results.LoadByScenarioID(ctx, conn, scope, scenarioID, cursor); err != nil {
|
|
return fmt.Errorf("cannot list scenario risks: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return page.NewPage(results, cursor), nil
|
|
}
|
|
|
|
func (s *Service) CountRisksForScenarioID(ctx context.Context, scope coredata.Scoper, scenarioID gid.GID) (int, error) {
|
|
var count int
|
|
|
|
err := s.pg.WithConn(
|
|
ctx,
|
|
func(ctx context.Context, conn pg.Querier) (err error) {
|
|
rs := &coredata.Risks{}
|
|
|
|
count, err = rs.CountByScenarioID(ctx, conn, scope, scenarioID)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot count scenario risks: %w", err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *Service) assertNodeInScope(
|
|
ctx context.Context,
|
|
tx pg.Tx,
|
|
scope coredata.Scoper,
|
|
nodeID gid.GID,
|
|
scopeID gid.GID,
|
|
field string,
|
|
) error {
|
|
node := &coredata.RiskAssessmentNode{}
|
|
if err := node.LoadByID(ctx, tx, scope, nodeID); err != nil {
|
|
return validator.ValidationErrors{{
|
|
Field: field,
|
|
Code: validator.ErrorCodeCustom,
|
|
Message: "node not found",
|
|
}}
|
|
}
|
|
|
|
if node.RiskAssessmentScopeID != scopeID {
|
|
return validator.ValidationErrors{{
|
|
Field: field,
|
|
Code: validator.ErrorCodeCustom,
|
|
Message: "node does not belong to this scope",
|
|
}}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) assertBoundaryInScope(
|
|
ctx context.Context,
|
|
tx pg.Tx,
|
|
scope coredata.Scoper,
|
|
boundaryID gid.GID,
|
|
scopeID gid.GID,
|
|
field string,
|
|
) error {
|
|
boundary := &coredata.RiskAssessmentBoundary{}
|
|
if err := boundary.LoadByID(ctx, tx, scope, boundaryID); err != nil {
|
|
return validator.ValidationErrors{{
|
|
Field: field,
|
|
Code: validator.ErrorCodeCustom,
|
|
Message: "boundary not found",
|
|
}}
|
|
}
|
|
|
|
// A boundary in a different scope is reported identically to a missing
|
|
// one so the error does not reveal that the resource exists elsewhere.
|
|
if boundary.RiskAssessmentScopeID != scopeID {
|
|
return validator.ValidationErrors{{
|
|
Field: field,
|
|
Code: validator.ErrorCodeCustom,
|
|
Message: "boundary not found",
|
|
}}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// assertNoBoundaryCycle walks the ancestor chain starting from the proposed
|
|
// parent. If it reaches the boundary being updated, the new parent would make
|
|
// the boundary an ancestor of itself (a cycle), which is rejected. A visited
|
|
// set guards against any pre-existing cycle in stored data.
|
|
func (s *Service) assertNoBoundaryCycle(
|
|
ctx context.Context,
|
|
tx pg.Tx,
|
|
scope coredata.Scoper,
|
|
boundaryID gid.GID,
|
|
proposedParentID gid.GID,
|
|
field string,
|
|
) error {
|
|
visited := make(map[gid.GID]bool)
|
|
currentID := proposedParentID
|
|
|
|
for {
|
|
if currentID == boundaryID {
|
|
return validator.ValidationErrors{{
|
|
Field: field,
|
|
Code: validator.ErrorCodeCustom,
|
|
Message: "boundary cannot be nested under itself or one of its descendants",
|
|
}}
|
|
}
|
|
|
|
if visited[currentID] {
|
|
return nil
|
|
}
|
|
|
|
visited[currentID] = true
|
|
|
|
current := &coredata.RiskAssessmentBoundary{}
|
|
if err := current.LoadByID(ctx, tx, scope, currentID); err != nil {
|
|
return fmt.Errorf("cannot load parent boundary: %w", err)
|
|
}
|
|
|
|
if current.ParentBoundaryID == nil {
|
|
return nil
|
|
}
|
|
|
|
currentID = *current.ParentBoundaryID
|
|
}
|
|
}
|
|
|
|
func (s *Service) assertProcessInScope(
|
|
ctx context.Context,
|
|
tx pg.Tx,
|
|
scope coredata.Scoper,
|
|
processID gid.GID,
|
|
scopeID gid.GID,
|
|
field string,
|
|
) error {
|
|
process := &coredata.RiskAssessmentProcess{}
|
|
if err := process.LoadByID(ctx, tx, scope, processID); err != nil {
|
|
return validator.ValidationErrors{{
|
|
Field: field,
|
|
Code: validator.ErrorCodeCustom,
|
|
Message: "process not found",
|
|
}}
|
|
}
|
|
|
|
if process.RiskAssessmentScopeID != scopeID {
|
|
return validator.ValidationErrors{{
|
|
Field: field,
|
|
Code: validator.ErrorCodeCustom,
|
|
Message: "process does not belong to this scope",
|
|
}}
|
|
}
|
|
|
|
return nil
|
|
}
|