@@ -29,7 +29,7 @@ import (
|
||||
type (
|
||||
Evidence struct {
|
||||
ID gid.GID `db:"id"`
|
||||
TaskID *gid.GID `db:"task_id"`
|
||||
TaskID gid.GID `db:"task_id"`
|
||||
State EvidenceState `db:"state"`
|
||||
Type EvidenceType `db:"type"`
|
||||
ObjectKey string `db:"object_key"`
|
||||
|
||||
@@ -24,9 +24,8 @@ type (
|
||||
)
|
||||
|
||||
const (
|
||||
EvidenceStateValid EvidenceState = iota
|
||||
EvidenceStateInvalid
|
||||
EvidenceStateExpired
|
||||
EvidenceStateRequested EvidenceState = iota
|
||||
EvidenceStateFulfilled
|
||||
)
|
||||
|
||||
func (es EvidenceState) MarshalText() ([]byte, error) {
|
||||
@@ -37,12 +36,10 @@ func (es *EvidenceState) UnmarshalText(data []byte) error {
|
||||
val := string(data)
|
||||
|
||||
switch val {
|
||||
case EvidenceStateValid.String():
|
||||
*es = EvidenceStateValid
|
||||
case EvidenceStateInvalid.String():
|
||||
*es = EvidenceStateInvalid
|
||||
case EvidenceStateExpired.String():
|
||||
*es = EvidenceStateExpired
|
||||
case EvidenceStateRequested.String():
|
||||
*es = EvidenceStateRequested
|
||||
case EvidenceStateFulfilled.String():
|
||||
*es = EvidenceStateFulfilled
|
||||
default:
|
||||
return fmt.Errorf("invalid EvidenceState value: %q", val)
|
||||
}
|
||||
@@ -54,12 +51,10 @@ func (es EvidenceState) String() string {
|
||||
var val string
|
||||
|
||||
switch es {
|
||||
case EvidenceStateValid:
|
||||
val = "VALID"
|
||||
case EvidenceStateInvalid:
|
||||
val = "INVALID"
|
||||
case EvidenceStateExpired:
|
||||
val = "EXPIRED"
|
||||
case EvidenceStateRequested:
|
||||
val = "REQUESTED"
|
||||
case EvidenceStateFulfilled:
|
||||
val = "FULFILLED"
|
||||
}
|
||||
|
||||
return val
|
||||
|
||||
10
pkg/coredata/migrations/20250402T083700Z.sql
Normal file
10
pkg/coredata/migrations/20250402T083700Z.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
CREATE TYPE evidence_state_new AS ENUM ('FULFILLED', 'REQUESTED');
|
||||
|
||||
ALTER TABLE evidences ALTER COLUMN state TYPE text;
|
||||
|
||||
UPDATE evidences SET state = 'FULFILLED';
|
||||
|
||||
ALTER TABLE evidences ALTER COLUMN state TYPE evidence_state_new USING state::evidence_state_new;
|
||||
|
||||
DROP TYPE evidence_state;
|
||||
ALTER TYPE evidence_state_new RENAME TO evidence_state;
|
||||
1
pkg/coredata/migrations/20250402T093900Z.sql
Normal file
1
pkg/coredata/migrations/20250402T093900Z.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE evidences ALTER COLUMN task_id SET NOT NULL;
|
||||
@@ -61,7 +61,7 @@ LOOP:
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(1 * time.Second):
|
||||
case <-time.After(60 * time.Second):
|
||||
ctx := context.Background()
|
||||
if err := m.batchSendEmails(ctx); err != nil {
|
||||
m.l.ErrorCtx(ctx, "cannot send email", log.Error(err))
|
||||
|
||||
@@ -37,6 +37,19 @@ type (
|
||||
svc *TenantService
|
||||
}
|
||||
|
||||
RequestEvidenceRequest struct {
|
||||
TaskID gid.GID
|
||||
Type coredata.EvidenceType
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
FulfilledEvidenceRequest struct {
|
||||
EvidenceID gid.GID
|
||||
File io.Reader
|
||||
URL string
|
||||
}
|
||||
|
||||
CreateEvidenceRequest struct {
|
||||
TaskID gid.GID
|
||||
Name string
|
||||
@@ -67,6 +80,63 @@ func (s EvidenceService) Get(
|
||||
return evidence, nil
|
||||
}
|
||||
|
||||
func (s EvidenceService) Request(
|
||||
ctx context.Context,
|
||||
req RequestEvidenceRequest,
|
||||
) (*coredata.Evidence, error) {
|
||||
evidenceID, err := gid.NewGID(s.svc.scope.GetTenantID(), coredata.EvidenceEntityType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create evidence global id: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
evidence := &coredata.Evidence{
|
||||
ID: evidenceID,
|
||||
TaskID: req.TaskID,
|
||||
State: coredata.EvidenceStateRequested,
|
||||
Type: req.Type,
|
||||
Filename: req.Name,
|
||||
Description: req.Description,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err = s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return evidence.Insert(ctx, conn, s.svc.scope)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot insert evidence: %w", err)
|
||||
}
|
||||
|
||||
return evidence, nil
|
||||
}
|
||||
|
||||
func (s EvidenceService) Fulfill(
|
||||
ctx context.Context,
|
||||
req FulfilledEvidenceRequest,
|
||||
) (*coredata.Evidence, error) {
|
||||
evidence := &coredata.Evidence{}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := evidence.LoadByID(ctx, conn, s.svc.scope, req.EvidenceID); err != nil {
|
||||
return fmt.Errorf("cannot load evidence: %w", err)
|
||||
}
|
||||
|
||||
evidence.State = coredata.EvidenceStateFulfilled
|
||||
|
||||
return evidence.Update(ctx, conn, s.svc.scope)
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func (s EvidenceService) Create(
|
||||
ctx context.Context,
|
||||
req CreateEvidenceRequest,
|
||||
@@ -79,8 +149,8 @@ func (s EvidenceService) Create(
|
||||
|
||||
evidence := &coredata.Evidence{
|
||||
ID: evidenceID,
|
||||
TaskID: &req.TaskID,
|
||||
State: coredata.EvidenceStateValid,
|
||||
TaskID: req.TaskID,
|
||||
State: coredata.EvidenceStateFulfilled,
|
||||
Type: req.Type,
|
||||
Filename: req.Name,
|
||||
URL: req.URL,
|
||||
|
||||
@@ -67,15 +67,13 @@ enum TaskState
|
||||
|
||||
enum EvidenceState
|
||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.EvidenceState") {
|
||||
VALID
|
||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.EvidenceStateValid")
|
||||
INVALID
|
||||
FULFILLED
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.EvidenceStateInvalid"
|
||||
value: "github.com/getprobo/probo/pkg/coredata.EvidenceStateFulfilled"
|
||||
)
|
||||
EXPIRED
|
||||
REQUESTED
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.EvidenceStateExpired"
|
||||
value: "github.com/getprobo/probo/pkg/coredata.EvidenceStateRequested"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -751,7 +749,9 @@ type Mutation {
|
||||
deleteRiskMapping(input: DeleteRiskMappingInput!): DeleteRiskMappingPayload!
|
||||
|
||||
# Evidence mutations
|
||||
uploadEvidence(input: UploadEvidenceInput!): UploadEvidencePayload!
|
||||
requestEvidence(input: RequestEvidenceInput!): RequestEvidencePayload!
|
||||
fulfillEvidence(input: FulfillEvidenceInput!): FulfillEvidencePayload!
|
||||
createEvidence(input: CreateEvidenceInput!): CreateEvidencePayload!
|
||||
deleteEvidence(input: DeleteEvidenceInput!): DeleteEvidencePayload!
|
||||
|
||||
# Policy mutations
|
||||
@@ -939,12 +939,25 @@ input DeleteRiskMappingInput {
|
||||
mitigationId: ID!
|
||||
}
|
||||
|
||||
input UploadEvidenceInput {
|
||||
input RequestEvidenceInput {
|
||||
taskId: ID!
|
||||
name: String!
|
||||
type: EvidenceType!
|
||||
description: String!
|
||||
}
|
||||
|
||||
input FulfillEvidenceInput {
|
||||
evidenceId: ID!
|
||||
file: Upload
|
||||
url: String
|
||||
}
|
||||
|
||||
input CreateEvidenceInput {
|
||||
taskId: ID!
|
||||
name: String!
|
||||
type: EvidenceType!
|
||||
url: String
|
||||
file: Upload
|
||||
description: String!
|
||||
}
|
||||
|
||||
@@ -1102,7 +1115,15 @@ type DeleteRiskMappingPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type UploadEvidencePayload {
|
||||
type RequestEvidencePayload {
|
||||
evidenceEdge: EvidenceEdge!
|
||||
}
|
||||
|
||||
type FulfillEvidencePayload {
|
||||
evidenceEdge: EvidenceEdge!
|
||||
}
|
||||
|
||||
type CreateEvidencePayload {
|
||||
evidenceEdge: EvidenceEdge!
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -68,6 +68,19 @@ type CreateControlMappingPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type CreateEvidenceInput struct {
|
||||
TaskID gid.GID `json:"taskId"`
|
||||
Name string `json:"name"`
|
||||
Type coredata.EvidenceType `json:"type"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
File *graphql.Upload `json:"file,omitempty"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type CreateEvidencePayload struct {
|
||||
EvidenceEdge *EvidenceEdge `json:"evidenceEdge"`
|
||||
}
|
||||
|
||||
type CreateFrameworkInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Name string `json:"name"`
|
||||
@@ -306,6 +319,16 @@ type FrameworkEdge struct {
|
||||
Node *Framework `json:"node"`
|
||||
}
|
||||
|
||||
type FulfillEvidenceInput struct {
|
||||
EvidenceID gid.GID `json:"evidenceId"`
|
||||
File *graphql.Upload `json:"file,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type FulfillEvidencePayload struct {
|
||||
EvidenceEdge *EvidenceEdge `json:"evidenceEdge"`
|
||||
}
|
||||
|
||||
type ImportFrameworkInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
File graphql.Upload `json:"file"`
|
||||
@@ -463,6 +486,17 @@ type RemoveUserPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type RequestEvidenceInput struct {
|
||||
TaskID gid.GID `json:"taskId"`
|
||||
Name string `json:"name"`
|
||||
Type coredata.EvidenceType `json:"type"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type RequestEvidencePayload struct {
|
||||
EvidenceEdge *EvidenceEdge `json:"evidenceEdge"`
|
||||
}
|
||||
|
||||
type Risk struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@@ -624,19 +658,6 @@ type UpdateVendorPayload struct {
|
||||
Vendor *Vendor `json:"vendor"`
|
||||
}
|
||||
|
||||
type UploadEvidenceInput struct {
|
||||
TaskID gid.GID `json:"taskId"`
|
||||
Name string `json:"name"`
|
||||
File *graphql.Upload `json:"file,omitempty"`
|
||||
Type coredata.EvidenceType `json:"type"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type UploadEvidencePayload struct {
|
||||
EvidenceEdge *EvidenceEdge `json:"evidenceEdge"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID gid.GID `json:"id"`
|
||||
FullName string `json:"fullName"`
|
||||
|
||||
@@ -718,20 +718,62 @@ func (r *mutationResolver) DeleteRiskMapping(ctx context.Context, input types.De
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UploadEvidence is the resolver for the uploadEvidence field.
|
||||
func (r *mutationResolver) UploadEvidence(ctx context.Context, input types.UploadEvidenceInput) (*types.UploadEvidencePayload, error) {
|
||||
// RequestEvidence is the resolver for the requestEvidence field.
|
||||
func (r *mutationResolver) RequestEvidence(ctx context.Context, input types.RequestEvidenceInput) (*types.RequestEvidencePayload, error) {
|
||||
svc := r.GetTenantServiceIfAuthorized(ctx, input.TaskID.TenantID())
|
||||
|
||||
var url string
|
||||
if input.URL != nil {
|
||||
url = *input.URL
|
||||
evidence, err := svc.Evidences.Request(
|
||||
ctx,
|
||||
probo.RequestEvidenceRequest{
|
||||
TaskID: input.TaskID,
|
||||
Name: input.Name,
|
||||
Type: input.Type,
|
||||
Description: input.Description,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot request evidence: %w", err))
|
||||
}
|
||||
|
||||
return &types.RequestEvidencePayload{
|
||||
EvidenceEdge: types.NewEvidenceEdge(evidence, coredata.EvidenceOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FulfillEvidence is the resolver for the fulfillEvidence field.
|
||||
func (r *mutationResolver) FulfillEvidence(ctx context.Context, input types.FulfillEvidenceInput) (*types.FulfillEvidencePayload, error) {
|
||||
svc := r.GetTenantServiceIfAuthorized(ctx, input.EvidenceID.TenantID())
|
||||
|
||||
req := probo.FulfilledEvidenceRequest{
|
||||
EvidenceID: input.EvidenceID,
|
||||
}
|
||||
|
||||
if input.File != nil {
|
||||
req.File = input.File.File
|
||||
}
|
||||
|
||||
if input.URL != nil {
|
||||
req.URL = *input.URL
|
||||
}
|
||||
|
||||
evidence, err := svc.Evidences.Fulfill(ctx, req)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot fulfill evidence: %w", err))
|
||||
}
|
||||
|
||||
return &types.FulfillEvidencePayload{
|
||||
EvidenceEdge: types.NewEvidenceEdge(evidence, coredata.EvidenceOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateEvidence is the resolver for the createEvidence field.
|
||||
func (r *mutationResolver) CreateEvidence(ctx context.Context, input types.CreateEvidenceInput) (*types.CreateEvidencePayload, error) {
|
||||
svc := r.GetTenantServiceIfAuthorized(ctx, input.TaskID.TenantID())
|
||||
|
||||
req := probo.CreateEvidenceRequest{
|
||||
TaskID: input.TaskID,
|
||||
Name: input.Name,
|
||||
Type: input.Type,
|
||||
URL: url,
|
||||
Description: input.Description,
|
||||
}
|
||||
|
||||
@@ -751,7 +793,7 @@ func (r *mutationResolver) UploadEvidence(ctx context.Context, input types.Uploa
|
||||
panic(fmt.Errorf("failed to create evidence: %w", err))
|
||||
}
|
||||
|
||||
return &types.UploadEvidencePayload{
|
||||
return &types.CreateEvidencePayload{
|
||||
EvidenceEdge: types.NewEvidenceEdge(evidence, coredata.EvidenceOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user