Add requested evidence

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-04-02 10:10:49 +02:00
parent 2f9f04c984
commit c145125b1a
15 changed files with 1856 additions and 352 deletions

View File

@@ -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"`

View File

@@ -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

View 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;

View File

@@ -0,0 +1 @@
ALTER TABLE evidences ALTER COLUMN task_id SET NOT NULL;

View File

@@ -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))

View File

@@ -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,

View File

@@ -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

View File

@@ -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"`

View File

@@ -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
}