Migrate audit reports to the files table
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
@@ -1456,9 +1456,9 @@ func TestAudit_UploadReport(t *testing.T) {
|
||||
uploadAuditReport(input: $input) {
|
||||
audit {
|
||||
id
|
||||
report {
|
||||
reportFile {
|
||||
id
|
||||
filename
|
||||
fileName
|
||||
size
|
||||
}
|
||||
}
|
||||
@@ -1472,12 +1472,12 @@ func TestAudit_UploadReport(t *testing.T) {
|
||||
var result struct {
|
||||
UploadAuditReport struct {
|
||||
Audit struct {
|
||||
ID string `json:"id"`
|
||||
Report *struct {
|
||||
ID string `json:"id"`
|
||||
ReportFile *struct {
|
||||
ID string `json:"id"`
|
||||
Filename string `json:"filename"`
|
||||
Size int `json:"size"`
|
||||
} `json:"report"`
|
||||
FileName string `json:"fileName"`
|
||||
Size int64 `json:"size"`
|
||||
} `json:"reportFile"`
|
||||
} `json:"audit"`
|
||||
} `json:"uploadAuditReport"`
|
||||
}
|
||||
@@ -1495,9 +1495,9 @@ func TestAudit_UploadReport(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, auditID, result.UploadAuditReport.Audit.ID)
|
||||
require.NotNil(t, result.UploadAuditReport.Audit.Report)
|
||||
assert.Equal(t, "audit-report.pdf", result.UploadAuditReport.Audit.Report.Filename)
|
||||
assert.Equal(t, len(pdfContent), result.UploadAuditReport.Audit.Report.Size)
|
||||
require.NotNil(t, result.UploadAuditReport.Audit.ReportFile)
|
||||
assert.Equal(t, "audit-report.pdf", result.UploadAuditReport.Audit.ReportFile.FileName)
|
||||
assert.Equal(t, int64(len(pdfContent)), result.UploadAuditReport.Audit.ReportFile.Size)
|
||||
})
|
||||
|
||||
t.Run("upload replaces existing report", func(t *testing.T) {
|
||||
@@ -1508,9 +1508,9 @@ func TestAudit_UploadReport(t *testing.T) {
|
||||
uploadAuditReport(input: $input) {
|
||||
audit {
|
||||
id
|
||||
report {
|
||||
reportFile {
|
||||
id
|
||||
filename
|
||||
fileName
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1523,11 +1523,11 @@ func TestAudit_UploadReport(t *testing.T) {
|
||||
var result1 struct {
|
||||
UploadAuditReport struct {
|
||||
Audit struct {
|
||||
ID string `json:"id"`
|
||||
Report *struct {
|
||||
ID string `json:"id"`
|
||||
ReportFile *struct {
|
||||
ID string `json:"id"`
|
||||
Filename string `json:"filename"`
|
||||
} `json:"report"`
|
||||
FileName string `json:"fileName"`
|
||||
} `json:"reportFile"`
|
||||
} `json:"audit"`
|
||||
} `json:"uploadAuditReport"`
|
||||
}
|
||||
@@ -1544,7 +1544,7 @@ func TestAudit_UploadReport(t *testing.T) {
|
||||
}, &result1)
|
||||
require.NoError(t, err)
|
||||
|
||||
firstReportID := result1.UploadAuditReport.Audit.Report.ID
|
||||
firstReportID := result1.UploadAuditReport.Audit.ReportFile.ID
|
||||
|
||||
// Upload second report (should replace)
|
||||
pdfContent2 := []byte("%PDF-1.4\n1 0 obj\n<< /Type /Catalog /Version /1.4 >>\nendobj\ntrailer\n<< /Root 1 0 R >>\n%%EOF")
|
||||
@@ -1552,11 +1552,11 @@ func TestAudit_UploadReport(t *testing.T) {
|
||||
var result2 struct {
|
||||
UploadAuditReport struct {
|
||||
Audit struct {
|
||||
ID string `json:"id"`
|
||||
Report *struct {
|
||||
ID string `json:"id"`
|
||||
ReportFile *struct {
|
||||
ID string `json:"id"`
|
||||
Filename string `json:"filename"`
|
||||
} `json:"report"`
|
||||
FileName string `json:"fileName"`
|
||||
} `json:"reportFile"`
|
||||
} `json:"audit"`
|
||||
} `json:"uploadAuditReport"`
|
||||
}
|
||||
@@ -1573,8 +1573,8 @@ func TestAudit_UploadReport(t *testing.T) {
|
||||
}, &result2)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "second-report.pdf", result2.UploadAuditReport.Audit.Report.Filename)
|
||||
assert.NotEqual(t, firstReportID, result2.UploadAuditReport.Audit.Report.ID, "Report ID should change when replaced")
|
||||
assert.Equal(t, "second-report.pdf", result2.UploadAuditReport.Audit.ReportFile.FileName)
|
||||
assert.NotEqual(t, firstReportID, result2.UploadAuditReport.Audit.ReportFile.ID, "Report file ID should change when replaced")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1782,7 +1782,7 @@ func TestAudit_DeleteReport(t *testing.T) {
|
||||
uploadAuditReport(input: $input) {
|
||||
audit {
|
||||
id
|
||||
report {
|
||||
reportFile {
|
||||
id
|
||||
}
|
||||
}
|
||||
@@ -1810,7 +1810,7 @@ func TestAudit_DeleteReport(t *testing.T) {
|
||||
deleteAuditReport(input: $input) {
|
||||
audit {
|
||||
id
|
||||
report {
|
||||
reportFile {
|
||||
id
|
||||
}
|
||||
}
|
||||
@@ -1821,10 +1821,10 @@ func TestAudit_DeleteReport(t *testing.T) {
|
||||
var deleteResult struct {
|
||||
DeleteAuditReport struct {
|
||||
Audit struct {
|
||||
ID string `json:"id"`
|
||||
Report *struct {
|
||||
ID string `json:"id"`
|
||||
ReportFile *struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"report"`
|
||||
} `json:"reportFile"`
|
||||
} `json:"audit"`
|
||||
} `json:"deleteAuditReport"`
|
||||
}
|
||||
@@ -1836,7 +1836,7 @@ func TestAudit_DeleteReport(t *testing.T) {
|
||||
}, &deleteResult)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, auditID, deleteResult.DeleteAuditReport.Audit.ID)
|
||||
assert.Nil(t, deleteResult.DeleteAuditReport.Audit.Report, "Report should be nil after deletion")
|
||||
assert.Nil(t, deleteResult.DeleteAuditReport.Audit.ReportFile, "Report file should be nil after deletion")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ type (
|
||||
Name *string `db:"name"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
FrameworkID gid.GID `db:"framework_id"`
|
||||
ReportID *gid.GID `db:"report_id"`
|
||||
ReportFileID *gid.GID `db:"report_file_id"`
|
||||
ValidFrom *time.Time `db:"valid_from"`
|
||||
ValidUntil *time.Time `db:"valid_until"`
|
||||
State AuditState `db:"state"`
|
||||
@@ -113,7 +113,7 @@ SELECT
|
||||
name,
|
||||
organization_id,
|
||||
framework_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
valid_from,
|
||||
valid_until,
|
||||
state,
|
||||
@@ -199,7 +199,7 @@ SELECT
|
||||
name,
|
||||
organization_id,
|
||||
framework_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
valid_from,
|
||||
valid_until,
|
||||
state,
|
||||
@@ -250,7 +250,7 @@ SELECT
|
||||
name,
|
||||
organization_id,
|
||||
framework_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
valid_from,
|
||||
valid_until,
|
||||
state,
|
||||
@@ -299,7 +299,7 @@ INSERT INTO audits (
|
||||
tenant_id,
|
||||
organization_id,
|
||||
framework_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
valid_from,
|
||||
valid_until,
|
||||
state,
|
||||
@@ -312,7 +312,7 @@ INSERT INTO audits (
|
||||
@tenant_id,
|
||||
@organization_id,
|
||||
@framework_id,
|
||||
@report_id,
|
||||
@report_file_id,
|
||||
@valid_from,
|
||||
@valid_until,
|
||||
@state,
|
||||
@@ -328,7 +328,7 @@ INSERT INTO audits (
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"organization_id": a.OrganizationID,
|
||||
"framework_id": a.FrameworkID,
|
||||
"report_id": a.ReportID,
|
||||
"report_file_id": a.ReportFileID,
|
||||
"valid_from": a.ValidFrom,
|
||||
"valid_until": a.ValidUntil,
|
||||
"state": a.State,
|
||||
@@ -354,7 +354,7 @@ func (a *Audit) Update(
|
||||
UPDATE audits
|
||||
SET
|
||||
name = @name,
|
||||
report_id = @report_id,
|
||||
report_file_id = @report_file_id,
|
||||
valid_from = @valid_from,
|
||||
valid_until = @valid_until,
|
||||
state = @state,
|
||||
@@ -370,7 +370,7 @@ WHERE
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": a.ID,
|
||||
"name": a.Name,
|
||||
"report_id": a.ReportID,
|
||||
"report_file_id": a.ReportFileID,
|
||||
"valid_from": a.ValidFrom,
|
||||
"valid_until": a.ValidUntil,
|
||||
"state": a.State,
|
||||
@@ -427,7 +427,7 @@ WITH audits_by_control AS (
|
||||
a.name,
|
||||
a.organization_id,
|
||||
a.framework_id,
|
||||
a.report_id,
|
||||
a.report_file_id,
|
||||
a.valid_from,
|
||||
a.valid_until,
|
||||
a.state,
|
||||
@@ -446,7 +446,7 @@ SELECT
|
||||
name,
|
||||
organization_id,
|
||||
framework_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
valid_from,
|
||||
valid_until,
|
||||
state,
|
||||
@@ -494,7 +494,7 @@ WITH audits_by_finding AS (
|
||||
a.name,
|
||||
a.organization_id,
|
||||
a.framework_id,
|
||||
a.report_id,
|
||||
a.report_file_id,
|
||||
a.valid_from,
|
||||
a.valid_until,
|
||||
a.state,
|
||||
@@ -513,7 +513,7 @@ SELECT
|
||||
name,
|
||||
organization_id,
|
||||
framework_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
valid_from,
|
||||
valid_until,
|
||||
state,
|
||||
@@ -632,11 +632,11 @@ WHERE
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (a *Audit) LoadByReportID(
|
||||
func (a *Audit) LoadByReportFileID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
reportID gid.GID,
|
||||
fileID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
@@ -644,7 +644,7 @@ SELECT
|
||||
name,
|
||||
organization_id,
|
||||
framework_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
valid_from,
|
||||
valid_until,
|
||||
state,
|
||||
@@ -654,12 +654,12 @@ SELECT
|
||||
FROM
|
||||
audits
|
||||
WHERE %s
|
||||
AND report_id = @report_id
|
||||
AND report_file_id = @report_file_id
|
||||
LIMIT 1;
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"report_id": reportID}
|
||||
args := pgx.StrictNamedArgs{"report_file_id": fileID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
@@ -681,6 +681,52 @@ LIMIT 1;
|
||||
return nil
|
||||
}
|
||||
|
||||
func (as *Audits) LoadByReportFileIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
fileIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
organization_id,
|
||||
framework_id,
|
||||
report_file_id,
|
||||
valid_from,
|
||||
valid_until,
|
||||
state,
|
||||
trust_center_visibility,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
audits
|
||||
WHERE
|
||||
%s
|
||||
AND report_file_id = ANY(@file_ids)
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"file_ids": fileIDs}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query audits by report file IDs: %w", err)
|
||||
}
|
||||
|
||||
audits, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Audit])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect audits by report file IDs: %w", err)
|
||||
}
|
||||
|
||||
*as = audits
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (as *Audits) LoadByReportIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
|
||||
@@ -53,8 +53,6 @@ func ResourceTypeName(entityType uint16) string {
|
||||
return "Datum"
|
||||
case AuditEntityType:
|
||||
return "Audit"
|
||||
case ReportEntityType:
|
||||
return "Report"
|
||||
case TrustCenterEntityType:
|
||||
return "TrustCenter"
|
||||
case TrustCenterAccessEntityType:
|
||||
|
||||
@@ -44,7 +44,7 @@ const (
|
||||
AssetEntityType uint16 = 18
|
||||
DatumEntityType uint16 = 19
|
||||
AuditEntityType uint16 = 20
|
||||
ReportEntityType uint16 = 21
|
||||
_ uint16 = 21 // ReportEntityType - removed
|
||||
TrustCenterEntityType uint16 = 22
|
||||
TrustCenterAccessEntityType uint16 = 23
|
||||
ThirdPartyBusinessAssociateAgreementEntityType uint16 = 24
|
||||
@@ -168,8 +168,6 @@ func NewEntityFromID(id gid.GID) (any, bool) {
|
||||
return &Datum{ID: id}, true
|
||||
case AuditEntityType:
|
||||
return &Audit{ID: id}, true
|
||||
case ReportEntityType:
|
||||
return &Report{ID: id}, true
|
||||
case TrustCenterEntityType:
|
||||
return &TrustCenter{ID: id}, true
|
||||
case TrustCenterAccessEntityType:
|
||||
|
||||
@@ -157,6 +157,10 @@ LIMIT 1;
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadByIDs Loads every given files, whether they are active or not. See
|
||||
// Files.LoadActiveByIDs for a safer option.
|
||||
//
|
||||
// DISCLAIMER: use with caution on user-facing features.
|
||||
func (f *Files) LoadByIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
@@ -269,6 +273,59 @@ VALUES (
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *File) LoadActiveByID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
fileID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
bucket_name,
|
||||
mime_type,
|
||||
file_name,
|
||||
file_key,
|
||||
file_size,
|
||||
visibility,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted_at
|
||||
FROM
|
||||
files
|
||||
WHERE
|
||||
%s
|
||||
AND id = @file_id
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"file_id": fileID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query file: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
file, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[File])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot collect file: %w", err)
|
||||
}
|
||||
|
||||
*f = file
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *File) LoadPublicByID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
|
||||
@@ -1,295 +0,0 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/iam/policy"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
Report struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
ObjectKey string `db:"object_key"`
|
||||
MimeType string `db:"mime_type"`
|
||||
Filename string `db:"filename"`
|
||||
Size int64 `db:"size"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
Reports []*Report
|
||||
)
|
||||
|
||||
func (r *Report) AuthorizationAttributes(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
resourceIDs []gid.GID,
|
||||
) (policy.AttributesByID, error) {
|
||||
q := `SELECT id, organization_id FROM reports WHERE id = ANY(@resource_ids::text[])`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"resource_ids": resourceIDs,
|
||||
}
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot query authorization attributes: %w", err)
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
attrsByID := make(policy.AttributesByID)
|
||||
|
||||
for rows.Next() {
|
||||
var id, organizationID gid.GID
|
||||
|
||||
if err := rows.Scan(&id, &organizationID); err != nil {
|
||||
return nil, fmt.Errorf("cannot scan authorization attributes: %w", err)
|
||||
}
|
||||
|
||||
attrsByID[id] = policy.Attributes{
|
||||
"organization_id": organizationID.String(),
|
||||
}
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("cannot iterate authorization attributes: %w", err)
|
||||
}
|
||||
|
||||
return attrsByID, nil
|
||||
}
|
||||
|
||||
func (r *Report) LoadByID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
reportID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
object_key,
|
||||
mime_type,
|
||||
filename,
|
||||
size,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
reports
|
||||
WHERE
|
||||
%s
|
||||
AND id = @report_id
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"report_id": reportID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query report: %w", err)
|
||||
}
|
||||
|
||||
report, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Report])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot collect report: %w", err)
|
||||
}
|
||||
|
||||
*r = report
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reports) LoadByIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
reportIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
object_key,
|
||||
mime_type,
|
||||
filename,
|
||||
size,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
reports
|
||||
WHERE
|
||||
%s
|
||||
AND id = ANY(@report_ids);
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"report_ids": reportIDs}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query report: %w", err)
|
||||
}
|
||||
|
||||
reports, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Report])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect reports: %w", err)
|
||||
}
|
||||
|
||||
*r = reports
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Report) Insert(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
INSERT INTO reports (
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
object_key,
|
||||
mime_type,
|
||||
filename,
|
||||
size,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@organization_id,
|
||||
@object_key,
|
||||
@mime_type,
|
||||
@filename,
|
||||
@size,
|
||||
@created_at,
|
||||
@updated_at
|
||||
)
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": r.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"organization_id": r.OrganizationID,
|
||||
"object_key": r.ObjectKey,
|
||||
"mime_type": r.MimeType,
|
||||
"filename": r.Filename,
|
||||
"size": r.Size,
|
||||
"created_at": r.CreatedAt,
|
||||
"updated_at": r.UpdatedAt,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert report: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Report) Update(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE reports
|
||||
SET
|
||||
object_key = @object_key,
|
||||
mime_type = @mime_type,
|
||||
filename = @filename,
|
||||
size = @size,
|
||||
updated_at = @updated_at
|
||||
WHERE
|
||||
%s
|
||||
AND id = @id
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": r.ID,
|
||||
"object_key": r.ObjectKey,
|
||||
"mime_type": r.MimeType,
|
||||
"filename": r.Filename,
|
||||
"size": r.Size,
|
||||
"updated_at": r.UpdatedAt,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update report: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Report) Delete(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
DELETE FROM reports
|
||||
WHERE
|
||||
%s
|
||||
AND id = @id
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"id": r.ID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete report: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Report) CursorKey(orderBy ReportOrderField) page.CursorKey {
|
||||
switch orderBy {
|
||||
case ReportOrderFieldID:
|
||||
return page.NewCursorKey(r.ID, r.ID)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
||||
}
|
||||
@@ -35,7 +35,7 @@ type (
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
TrustCenterAccessID gid.GID `db:"trust_center_access_id"`
|
||||
DocumentID *gid.GID `db:"document_id"`
|
||||
ReportID *gid.GID `db:"report_id"`
|
||||
ReportFileID *gid.GID `db:"report_file_id"`
|
||||
TrustCenterFileID *gid.GID `db:"trust_center_file_id"`
|
||||
Status TrustCenterDocumentAccessStatus `db:"status"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
@@ -105,7 +105,7 @@ SELECT
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
trust_center_file_id,
|
||||
status,
|
||||
created_at,
|
||||
@@ -155,7 +155,7 @@ SELECT
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
trust_center_file_id,
|
||||
status,
|
||||
created_at,
|
||||
@@ -196,12 +196,12 @@ LIMIT 1;
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tcda *TrustCenterDocumentAccess) LoadByTrustCenterAccessIDAndReportID(
|
||||
func (tcda *TrustCenterDocumentAccess) LoadByTrustCenterAccessIDAndReportFileID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
trustCenterAccessID gid.GID,
|
||||
reportID gid.GID,
|
||||
reportFileID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
@@ -209,7 +209,7 @@ SELECT
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
trust_center_file_id,
|
||||
status,
|
||||
created_at,
|
||||
@@ -219,7 +219,7 @@ FROM
|
||||
WHERE
|
||||
%s
|
||||
AND trust_center_access_id = @trust_center_access_id
|
||||
AND report_id = @report_id
|
||||
AND report_file_id = @report_file_id
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
@@ -227,7 +227,7 @@ LIMIT 1;
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"trust_center_access_id": trustCenterAccessID,
|
||||
"report_id": reportID,
|
||||
"report_file_id": reportFileID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
@@ -262,7 +262,7 @@ INSERT INTO trust_center_document_accesses (
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
trust_center_file_id,
|
||||
status,
|
||||
created_at,
|
||||
@@ -273,7 +273,7 @@ INSERT INTO trust_center_document_accesses (
|
||||
@organization_id,
|
||||
@trust_center_access_id,
|
||||
@document_id,
|
||||
@report_id,
|
||||
@report_file_id,
|
||||
@trust_center_file_id,
|
||||
@status::trust_center_document_access_status,
|
||||
@created_at,
|
||||
@@ -287,7 +287,7 @@ INSERT INTO trust_center_document_accesses (
|
||||
"organization_id": tcda.OrganizationID,
|
||||
"trust_center_access_id": tcda.TrustCenterAccessID,
|
||||
"document_id": tcda.DocumentID,
|
||||
"report_id": tcda.ReportID,
|
||||
"report_file_id": tcda.ReportFileID,
|
||||
"trust_center_file_id": tcda.TrustCenterFileID,
|
||||
"status": tcda.Status,
|
||||
"created_at": tcda.CreatedAt,
|
||||
@@ -300,7 +300,7 @@ INSERT INTO trust_center_document_accesses (
|
||||
if pgErr.Code == "23505" {
|
||||
switch pgErr.ConstraintName {
|
||||
case "trust_center_document_accesse_trust_center_access_id_docume_key",
|
||||
"trust_center_document_accesse_trust_center_access_id_report_key",
|
||||
"trust_center_document_accesses_trust_center_access_id_report_file_key",
|
||||
"trust_center_document_accesses_trust_center_file_id_key":
|
||||
return ErrResourceAlreadyExists
|
||||
}
|
||||
@@ -496,7 +496,7 @@ all_items AS (
|
||||
SELECT
|
||||
d.id AS item_id,
|
||||
d.id AS document_id,
|
||||
NULL::text AS report_id,
|
||||
NULL::text AS report_file_id,
|
||||
NULL::text AS trust_center_file_id,
|
||||
d.created_at AS item_created_at,
|
||||
d.updated_at AS item_updated_at
|
||||
@@ -508,23 +508,23 @@ all_items AS (
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
r.report_id AS item_id,
|
||||
r.report_file_id AS item_id,
|
||||
NULL::text AS document_id,
|
||||
r.report_id AS report_id,
|
||||
r.report_file_id AS report_file_id,
|
||||
NULL::text AS trust_center_file_id,
|
||||
r.created_at AS item_created_at,
|
||||
r.updated_at AS item_updated_at
|
||||
FROM audits r, tenant_organization o
|
||||
WHERE r.organization_id = o.organization_id
|
||||
AND r.trust_center_visibility = 'PRIVATE'::trust_center_visibility
|
||||
AND r.report_id IS NOT NULL
|
||||
AND r.report_file_id IS NOT NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
tcf.id AS item_id,
|
||||
NULL::text AS document_id,
|
||||
NULL::text AS report_id,
|
||||
NULL::text AS report_file_id,
|
||||
tcf.id AS trust_center_file_id,
|
||||
tcf.created_at AS item_created_at,
|
||||
tcf.updated_at AS item_updated_at
|
||||
@@ -542,7 +542,7 @@ final_items AS (
|
||||
(SELECT organization_id FROM organization) AS organization_id,
|
||||
@trust_center_access_id AS trust_center_access_id,
|
||||
ai.document_id,
|
||||
ai.report_id,
|
||||
ai.report_file_id,
|
||||
ai.trust_center_file_id,
|
||||
COALESCE(tcda.status, 'REQUESTED'::trust_center_document_access_status) AS status,
|
||||
COALESCE(tcda.created_at, ai.item_created_at) AS created_at,
|
||||
@@ -552,7 +552,7 @@ final_items AS (
|
||||
tcda.trust_center_access_id = @trust_center_access_id
|
||||
AND (
|
||||
(tcda.document_id = ai.document_id AND ai.document_id IS NOT NULL)
|
||||
OR (tcda.report_id = ai.report_id AND ai.report_id IS NOT NULL)
|
||||
OR (tcda.report_file_id = ai.report_file_id AND ai.report_file_id IS NOT NULL)
|
||||
OR (tcda.trust_center_file_id = ai.trust_center_file_id AND ai.trust_center_file_id IS NOT NULL)
|
||||
)
|
||||
)
|
||||
@@ -562,7 +562,7 @@ SELECT
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
trust_center_file_id,
|
||||
status,
|
||||
created_at,
|
||||
@@ -606,7 +606,7 @@ SELECT
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
trust_center_file_id,
|
||||
status,
|
||||
created_at,
|
||||
@@ -714,12 +714,12 @@ WHERE
|
||||
return nil
|
||||
}
|
||||
|
||||
func GrantByReportIDs(
|
||||
func GrantByReportFileIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
trustCenterAccessID gid.GID,
|
||||
reportIDs []gid.GID,
|
||||
reportFileIDs []gid.GID,
|
||||
updatedAt time.Time,
|
||||
) error {
|
||||
q := `
|
||||
@@ -728,32 +728,32 @@ SET status = 'GRANTED'::trust_center_document_access_status, updated_at = @updat
|
||||
WHERE
|
||||
%s
|
||||
AND trust_center_access_id = @trust_center_access_id
|
||||
AND report_id = ANY(@report_ids)
|
||||
AND report_file_id = ANY(@report_file_ids)
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"trust_center_access_id": trustCenterAccessID,
|
||||
"report_ids": reportIDs,
|
||||
"report_file_ids": reportFileIDs,
|
||||
"updated_at": updatedAt,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot grant trust center document accesses by report IDs: %w", err)
|
||||
return fmt.Errorf("cannot grant trust center document accesses by report file IDs: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RejectOrRevokeByReportIDs(
|
||||
func RejectOrRevokeByReportFileIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
trustCenterAccessID gid.GID,
|
||||
reportIDs []gid.GID,
|
||||
reportFileIDs []gid.GID,
|
||||
updatedAt time.Time,
|
||||
) error {
|
||||
q := `
|
||||
@@ -767,21 +767,21 @@ SET
|
||||
WHERE
|
||||
%s
|
||||
AND trust_center_access_id = @trust_center_access_id
|
||||
AND report_id = ANY(@report_ids)
|
||||
AND report_file_id = ANY(@report_file_ids)
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"trust_center_access_id": trustCenterAccessID,
|
||||
"report_ids": reportIDs,
|
||||
"report_file_ids": reportFileIDs,
|
||||
"updated_at": updatedAt,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot reject trust center document accesses by report IDs: %w", err)
|
||||
return fmt.Errorf("cannot reject trust center document accesses by report file IDs: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -829,7 +829,7 @@ WHEN NOT MATCHED
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
trust_center_file_id,
|
||||
status,
|
||||
created_at,
|
||||
@@ -887,7 +887,7 @@ WITH document_access_data AS (
|
||||
@organization_id AS organization_id,
|
||||
@trust_center_access_id AS trust_center_access_id,
|
||||
unnest(@document_ids::text[]) AS document_id,
|
||||
null::text AS report_id,
|
||||
null::text AS report_file_id,
|
||||
null::text AS trust_center_file_id,
|
||||
@status::trust_center_document_access_status AS status,
|
||||
@created_at::timestamptz AS created_at,
|
||||
@@ -899,7 +899,7 @@ INSERT INTO trust_center_document_accesses (
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
trust_center_file_id,
|
||||
status,
|
||||
created_at,
|
||||
@@ -927,7 +927,7 @@ ON CONFLICT DO NOTHING
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tcdas TrustCenterDocumentAccesses) MergeReportAccesses(
|
||||
func (tcdas TrustCenterDocumentAccesses) MergeReportFileAccesses(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
@@ -947,7 +947,7 @@ WITH data AS (
|
||||
)
|
||||
MERGE INTO trust_center_document_accesses AS tcda
|
||||
USING data
|
||||
ON data.id = tcda.report_id
|
||||
ON data.id = tcda.report_file_id
|
||||
AND tcda.tenant_id = @tenant_id
|
||||
AND tcda.trust_center_access_id = @trust_center_access_id
|
||||
WHEN MATCHED
|
||||
@@ -955,7 +955,7 @@ WHEN MATCHED
|
||||
WHEN NOT MATCHED BY SOURCE
|
||||
AND tcda.tenant_id = @tenant_id
|
||||
AND tcda.trust_center_access_id = @trust_center_access_id
|
||||
AND tcda.report_id IS NOT NULL
|
||||
AND tcda.report_file_id IS NOT NULL
|
||||
THEN DELETE
|
||||
WHEN NOT MATCHED
|
||||
THEN INSERT (
|
||||
@@ -964,7 +964,7 @@ WHEN NOT MATCHED
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
trust_center_file_id,
|
||||
status,
|
||||
created_at,
|
||||
@@ -1000,29 +1000,29 @@ WHEN NOT MATCHED
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tcdas TrustCenterDocumentAccesses) BulkInsertReportAccesses(
|
||||
func (tcdas TrustCenterDocumentAccesses) BulkInsertReportFileAccesses(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
trustCenterAccessID gid.GID,
|
||||
organizationID gid.GID,
|
||||
reportIDs []gid.GID,
|
||||
reportFileIDs []gid.GID,
|
||||
status TrustCenterDocumentAccessStatus,
|
||||
createdAt time.Time,
|
||||
) error {
|
||||
if len(reportIDs) == 0 {
|
||||
if len(reportFileIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
q := `
|
||||
WITH report_access_data AS (
|
||||
WITH report_file_access_data AS (
|
||||
SELECT
|
||||
generate_gid(decode_base64_unpadded(@tenant_id), @trust_center_document_access_entity_type) AS id,
|
||||
@tenant_id AS tenant_id,
|
||||
@organization_id AS organization_id,
|
||||
@trust_center_access_id AS trust_center_access_id,
|
||||
null::text AS document_id,
|
||||
unnest(@report_ids::text[]) AS report_id,
|
||||
unnest(@report_file_ids::text[]) AS report_file_id,
|
||||
null::text AS trust_center_file_id,
|
||||
@status::trust_center_document_access_status AS status,
|
||||
@created_at::timestamptz AS created_at,
|
||||
@@ -1034,13 +1034,13 @@ INSERT INTO trust_center_document_accesses (
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
trust_center_file_id,
|
||||
status,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT * FROM report_access_data
|
||||
SELECT * FROM report_file_access_data
|
||||
ON CONFLICT DO NOTHING
|
||||
`
|
||||
|
||||
@@ -1049,14 +1049,14 @@ ON CONFLICT DO NOTHING
|
||||
"organization_id": organizationID,
|
||||
"trust_center_document_access_entity_type": TrustCenterDocumentAccessEntityType,
|
||||
"trust_center_access_id": trustCenterAccessID,
|
||||
"report_ids": reportIDs,
|
||||
"report_file_ids": reportFileIDs,
|
||||
"status": status,
|
||||
"created_at": createdAt,
|
||||
"updated_at": createdAt,
|
||||
}
|
||||
|
||||
if _, err := conn.Exec(ctx, q, args); err != nil {
|
||||
return fmt.Errorf("cannot bulk insert trust center report accesses: %w", err)
|
||||
return fmt.Errorf("cannot bulk insert trust center report file accesses: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1075,7 +1075,7 @@ SELECT
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
trust_center_file_id,
|
||||
status,
|
||||
created_at,
|
||||
@@ -1226,7 +1226,7 @@ WHEN NOT MATCHED
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
trust_center_file_id,
|
||||
status,
|
||||
created_at,
|
||||
@@ -1280,7 +1280,7 @@ WITH trust_center_file_access_data AS (
|
||||
@organization_id AS organization_id,
|
||||
@trust_center_access_id AS trust_center_access_id,
|
||||
null::text AS document_id,
|
||||
null::text AS report_id,
|
||||
null::text AS report_file_id,
|
||||
unnest(@trust_center_file_ids::text[]) AS trust_center_file_id,
|
||||
@status::trust_center_document_access_status AS status,
|
||||
@created_at::timestamptz AS created_at,
|
||||
@@ -1292,7 +1292,7 @@ INSERT INTO trust_center_document_accesses (
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
report_file_id,
|
||||
trust_center_file_id,
|
||||
status,
|
||||
created_at,
|
||||
|
||||
@@ -19,8 +19,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"go.gearno.de/crypto/uuid"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/filevalidation"
|
||||
@@ -113,7 +111,11 @@ func (s AuditService) Get(
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
return audit.LoadByID(ctx, conn, scope, auditID)
|
||||
if err := audit.LoadByID(ctx, conn, scope, auditID); err != nil {
|
||||
return fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -123,16 +125,20 @@ func (s AuditService) Get(
|
||||
return audit, nil
|
||||
}
|
||||
|
||||
func (s AuditService) GetByReportID(
|
||||
func (s AuditService) GetByReportFileID(
|
||||
ctx context.Context, scope coredata.Scoper,
|
||||
reportID gid.GID,
|
||||
fileID gid.GID,
|
||||
) (*coredata.Audit, error) {
|
||||
audit := &coredata.Audit{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
return audit.LoadByReportID(ctx, conn, scope, reportID)
|
||||
if err := audit.LoadByReportFileID(ctx, conn, scope, fileID); err != nil {
|
||||
return fmt.Errorf("cannot load report file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -326,7 +332,7 @@ func (s AuditService) CountForOrganizationID(
|
||||
|
||||
func (s AuditService) UploadReport(
|
||||
ctx context.Context, scope coredata.Scoper,
|
||||
req UploadAuditReportRequest,
|
||||
req *UploadAuditReportRequest,
|
||||
) (*coredata.Audit, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %w", err)
|
||||
@@ -334,60 +340,52 @@ func (s AuditService) UploadReport(
|
||||
|
||||
audit := &coredata.Audit{}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
err := audit.LoadByID(ctx, conn, scope, req.AuditID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fv := filevalidation.NewValidator(
|
||||
filevalidation.WithCategories(filevalidation.CategoryDocument),
|
||||
filevalidation.WithMaxFileSize(25*1024*1024),
|
||||
)
|
||||
|
||||
file, err := s.svc.Files.UploadAndSaveFile(
|
||||
ctx, scope,
|
||||
fv,
|
||||
map[string]string{"organization-id": audit.OrganizationID.String()},
|
||||
&FileUpload{
|
||||
Content: req.File.Content,
|
||||
Filename: req.File.Filename,
|
||||
Size: req.File.Size,
|
||||
ContentType: req.File.ContentType,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot upload file: %w", err)
|
||||
}
|
||||
|
||||
err = s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Tx) error {
|
||||
if err := audit.LoadByID(ctx, conn, scope, req.AuditID); err != nil {
|
||||
return fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
|
||||
reportID := gid.New(scope.GetTenantID(), coredata.ReportEntityType)
|
||||
now := time.Now()
|
||||
|
||||
objectKey, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot generate object key: %w", err)
|
||||
}
|
||||
|
||||
_, err = s.svc.s3.PutObject(ctx, &s3.PutObjectInput{
|
||||
Bucket: new(s.svc.bucket),
|
||||
Key: new(objectKey.String()),
|
||||
Body: req.File.Content,
|
||||
ContentType: new(req.File.ContentType),
|
||||
CacheControl: new("private, max-age=3600"),
|
||||
Metadata: map[string]string{
|
||||
"type": "report",
|
||||
"report-id": reportID.String(),
|
||||
"organization-id": audit.OrganizationID.String(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot upload report to S3: %w", err)
|
||||
}
|
||||
|
||||
report := &coredata.Report{
|
||||
ID: reportID,
|
||||
OrganizationID: audit.OrganizationID,
|
||||
ObjectKey: objectKey.String(),
|
||||
MimeType: req.File.ContentType,
|
||||
Filename: req.File.Filename,
|
||||
Size: req.File.Size,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := report.Insert(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot insert report: %w", err)
|
||||
}
|
||||
|
||||
audit.ReportID = &report.ID
|
||||
audit.ReportFileID = &file.ID
|
||||
audit.UpdatedAt = time.Now()
|
||||
|
||||
if err := audit.Update(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot update audit: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return audit.Update(ctx, conn, scope)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -407,16 +405,16 @@ func (s AuditService) GenerateReportURL(
|
||||
return nil, fmt.Errorf("cannot get audit: %w", err)
|
||||
}
|
||||
|
||||
if audit.ReportID == nil {
|
||||
if audit.ReportFileID == nil {
|
||||
return nil, fmt.Errorf("audit has no report")
|
||||
}
|
||||
|
||||
url, err := s.svc.Reports.GenerateDownloadURL(ctx, scope, *audit.ReportID, expiresIn)
|
||||
url, err := s.svc.Files.GenerateFileTempURL(ctx, scope, *audit.ReportFileID, expiresIn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot generate report download URL: %w", err)
|
||||
}
|
||||
|
||||
return url, nil
|
||||
return &url, nil
|
||||
}
|
||||
|
||||
func (s AuditService) DeleteReport(
|
||||
@@ -425,21 +423,21 @@ func (s AuditService) DeleteReport(
|
||||
) (*coredata.Audit, error) {
|
||||
audit := &coredata.Audit{}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
return audit, s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Tx) error {
|
||||
if err := audit.LoadByID(ctx, conn, scope, auditID); err != nil {
|
||||
return fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
|
||||
if audit.ReportID != nil {
|
||||
report := &coredata.Report{ID: *audit.ReportID}
|
||||
if audit.ReportFileID != nil {
|
||||
file := coredata.File{ID: *audit.ReportFileID}
|
||||
|
||||
if err := report.Delete(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot delete report: %w", err)
|
||||
if err := file.SoftDelete(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot soft-delete report file: %w", err)
|
||||
}
|
||||
|
||||
audit.ReportID = nil
|
||||
audit.ReportFileID = nil
|
||||
audit.UpdatedAt = time.Now()
|
||||
|
||||
if err := audit.Update(ctx, conn, scope); err != nil {
|
||||
@@ -450,11 +448,6 @@ func (s AuditService) DeleteReport(
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return audit, nil
|
||||
}
|
||||
|
||||
func (s AuditService) ListForControlID(
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package probo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type ReportService struct {
|
||||
svc *Service
|
||||
}
|
||||
|
||||
func (s ReportService) Get(
|
||||
ctx context.Context, scope coredata.Scoper,
|
||||
reportID gid.GID,
|
||||
) (*coredata.Report, error) {
|
||||
report := &coredata.Report{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
err := report.LoadByID(ctx, conn, scope, reportID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load report: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (s ReportService) GetByIDs(
|
||||
ctx context.Context, scope coredata.Scoper,
|
||||
reportIDs ...gid.GID,
|
||||
) (coredata.Reports, error) {
|
||||
var reports coredata.Reports
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
if err := reports.LoadByIDs(
|
||||
ctx,
|
||||
conn,
|
||||
scope,
|
||||
reportIDs,
|
||||
); err != nil {
|
||||
return fmt.Errorf("cannot load reports by ids: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
func (s ReportService) Delete(
|
||||
ctx context.Context, scope coredata.Scoper,
|
||||
reportID gid.GID,
|
||||
) error {
|
||||
return s.svc.pg.WithTx(ctx, func(ctx context.Context, conn pg.Tx) error {
|
||||
report := &coredata.Report{}
|
||||
|
||||
err := report.LoadByID(ctx, conn, scope, reportID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get report: %w", err)
|
||||
}
|
||||
|
||||
err = report.Delete(ctx, conn, scope)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete report: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s ReportService) GenerateDownloadURL(
|
||||
ctx context.Context, scope coredata.Scoper,
|
||||
reportID gid.GID,
|
||||
expiresIn time.Duration,
|
||||
) (*string, error) {
|
||||
report, err := s.Get(ctx, scope, reportID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get report: %w", err)
|
||||
}
|
||||
|
||||
presignClient := s3.NewPresignClient(s.svc.s3)
|
||||
|
||||
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: new(s.svc.bucket),
|
||||
Key: new(report.ObjectKey),
|
||||
ResponseCacheControl: new("max-age=3600, public"),
|
||||
ResponseContentType: new(report.MimeType),
|
||||
ResponseContentDisposition: new(fmt.Sprintf("attachment; filename=\"%s\"", report.Filename)),
|
||||
}, func(opts *s3.PresignOptions) {
|
||||
opts.Expires = expiresIn
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot presign GetObject request: %w", err)
|
||||
}
|
||||
|
||||
return &presignedReq.URL, nil
|
||||
}
|
||||
@@ -106,7 +106,6 @@ type (
|
||||
Data *DatumService
|
||||
Audits *AuditService
|
||||
WebhookSubscriptions *WebhookSubscriptionService
|
||||
Reports *ReportService
|
||||
TrustCenters *TrustCenterService
|
||||
TrustCenterAccesses *TrustCenterAccessService
|
||||
TrustCenterReferences *TrustCenterReferenceService
|
||||
@@ -230,7 +229,6 @@ func NewService(
|
||||
svc.Data = &DatumService{svc: svc}
|
||||
svc.Audits = &AuditService{svc: svc}
|
||||
svc.WebhookSubscriptions = &WebhookSubscriptionService{svc: svc}
|
||||
svc.Reports = &ReportService{svc: svc}
|
||||
svc.TrustCenters = &TrustCenterService{svc: svc}
|
||||
svc.TrustCenterAccesses = &TrustCenterAccessService{svc: svc}
|
||||
svc.TrustCenterReferences = &TrustCenterReferenceService{svc: svc}
|
||||
|
||||
@@ -68,7 +68,7 @@ func (utcar *UpdateTrustCenterAccessRequest) Validate() error {
|
||||
}
|
||||
|
||||
for i, reportAccess := range utcar.ReportAccesses {
|
||||
v.Check(reportAccess.ID, fmt.Sprintf("reportAccesses[%d].ID", i), validator.Required(), validator.GID(coredata.ReportEntityType))
|
||||
v.Check(reportAccess.ID, fmt.Sprintf("reportAccesses[%d].ID", i), validator.Required(), validator.GID(coredata.FileEntityType))
|
||||
}
|
||||
|
||||
for i, reportAccess := range utcar.TrustCenterFileAccesses {
|
||||
@@ -263,7 +263,7 @@ func (s TrustCenterAccessService) Update(
|
||||
})
|
||||
}
|
||||
|
||||
if err := tcdas.MergeReportAccesses(ctx, tx, scope, access.OrganizationID, access.ID, reportData); err != nil {
|
||||
if err := tcdas.MergeReportFileAccesses(ctx, tx, scope, access.OrganizationID, access.ID, reportData); err != nil {
|
||||
return fmt.Errorf("cannot merge report accesses: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ package console_v1
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/vikstrous/dataloadgen"
|
||||
"go.gearno.de/kit/log"
|
||||
@@ -68,50 +67,30 @@ func (r *auditResolver) Framework(ctx context.Context, obj *types.Audit) (*types
|
||||
return types.NewFramework(framework), nil
|
||||
}
|
||||
|
||||
// Report is the resolver for the report field.
|
||||
func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Report, error) {
|
||||
// ReportFile is the resolver for the reportFile field.
|
||||
func (r *auditResolver) ReportFile(ctx context.Context, obj *types.Audit) (*types.File, error) {
|
||||
if _, err := r.authorize(ctx, obj.ID, probo.ActionReportGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if obj.Report == nil {
|
||||
if obj.ReportFile == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
loaders := dataloader.FromContext(ctx)
|
||||
|
||||
report, err := loaders.Report.Load(ctx, obj.Report.ID)
|
||||
file, err := loaders.File.Load(ctx, obj.ReportFile.ID)
|
||||
if err != nil {
|
||||
if errors.Is(err, dataloadgen.ErrNotFound) {
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot load report", log.Error(err))
|
||||
r.logger.ErrorCtx(ctx, "cannot load report file", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewReport(report), nil
|
||||
}
|
||||
|
||||
// ReportURL is the resolver for the reportUrl field.
|
||||
func (r *auditResolver) ReportURL(ctx context.Context, obj *types.Audit) (*string, error) {
|
||||
scope, err := r.authorize(ctx, obj.ID, probo.ActionReportGetReportUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if obj.Report == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
url, err := r.probo.Audits.GenerateReportURL(ctx, scope, obj.ID, 15*time.Minute)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot generate report URL", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return url, nil
|
||||
return types.NewFile(file), nil
|
||||
}
|
||||
|
||||
// Controls is the resolver for the controls field.
|
||||
@@ -431,7 +410,7 @@ func (r *mutationResolver) CreateAudit(ctx context.Context, input types.CreateAu
|
||||
},
|
||||
}
|
||||
|
||||
audit, err = r.probo.Audits.UploadReport(ctx, scope, uploadReq)
|
||||
audit, err = r.probo.Audits.UploadReport(ctx, scope, &uploadReq)
|
||||
if err != nil {
|
||||
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
||||
@@ -514,7 +493,7 @@ func (r *mutationResolver) UploadAuditReport(ctx context.Context, input types.Up
|
||||
},
|
||||
}
|
||||
|
||||
audit, err := r.probo.Audits.UploadReport(ctx, scope, req)
|
||||
audit, err := r.probo.Audits.UploadReport(ctx, scope, &req)
|
||||
if err != nil {
|
||||
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
||||
@@ -704,43 +683,6 @@ func (r *mutationResolver) PublishFindingList(ctx context.Context, input types.P
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DownloadURL is the resolver for the downloadUrl field.
|
||||
func (r *reportResolver) DownloadURL(ctx context.Context, obj *types.Report) (*string, error) {
|
||||
scope, err := r.authorize(ctx, obj.ID, probo.ActionReportDownloadUrlGet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url, err := r.probo.Reports.GenerateDownloadURL(ctx, scope, obj.ID, 15*time.Minute)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot generate download URL", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// Audit is the resolver for the audit field.
|
||||
func (r *reportResolver) Audit(ctx context.Context, obj *types.Report) (*types.Audit, error) {
|
||||
scope, err := r.authorize(ctx, obj.ID, probo.ActionAuditGet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
audit, err := r.probo.Audits.GetByReportID(ctx, scope, obj.ID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot load audit for report", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewAudit(audit), nil
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *reportResolver) Permission(ctx context.Context, obj *types.Report, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// Audit returns schema.AuditResolver implementation.
|
||||
func (r *Resolver) Audit() schema.AuditResolver { return &auditResolver{r} }
|
||||
|
||||
@@ -757,11 +699,7 @@ func (r *Resolver) FindingConnection() schema.FindingConnectionResolver {
|
||||
return &findingConnectionResolver{r}
|
||||
}
|
||||
|
||||
// Report returns schema.ReportResolver implementation.
|
||||
func (r *Resolver) Report() schema.ReportResolver { return &reportResolver{r} }
|
||||
|
||||
type auditResolver struct{ *Resolver }
|
||||
type auditConnectionResolver struct{ *Resolver }
|
||||
type findingResolver struct{ *Resolver }
|
||||
type findingConnectionResolver struct{ *Resolver }
|
||||
type reportResolver struct{ *Resolver }
|
||||
|
||||
@@ -279,16 +279,6 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
||||
|
||||
return types.NewObligation(obligation), nil
|
||||
}
|
||||
case coredata.ReportEntityType:
|
||||
action = probo.ActionReportGet
|
||||
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
|
||||
report, err := r.probo.Reports.Get(ctx, scope, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewReport(report), nil
|
||||
}
|
||||
case coredata.ProcessingActivityEntityType:
|
||||
action = probo.ActionProcessingActivityList
|
||||
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
|
||||
|
||||
@@ -62,7 +62,6 @@ type (
|
||||
Measure *dataloadgen.Loader[gid.GID, *coredata.Measure]
|
||||
Task *dataloadgen.Loader[gid.GID, *coredata.Task]
|
||||
File *dataloadgen.Loader[gid.GID, *coredata.File]
|
||||
Report *dataloadgen.Loader[gid.GID, *coredata.Report]
|
||||
CookieBanner *dataloadgen.Loader[gid.GID, *coredata.CookieBanner]
|
||||
CookieCategory *dataloadgen.Loader[gid.GID, *coredata.CookieCategory]
|
||||
CommonTrackerPattern *dataloadgen.Loader[gid.GID, *coredata.CommonTrackerPattern]
|
||||
@@ -114,7 +113,6 @@ func (f *batchFetcher) newLoaders() *Loaders {
|
||||
Measure: dataloadgen.NewMappedLoader(f.fetchMeasures),
|
||||
Task: dataloadgen.NewMappedLoader(f.fetchTasks),
|
||||
File: dataloadgen.NewMappedLoader(f.fetchFiles),
|
||||
Report: dataloadgen.NewMappedLoader(f.fetchReports),
|
||||
CookieBanner: dataloadgen.NewMappedLoader(f.fetchCookieBanners),
|
||||
CookieCategory: dataloadgen.NewMappedLoader(f.fetchCookieCategories),
|
||||
CommonTrackerPattern: dataloadgen.NewMappedLoader(f.fetchCommonTrackerPatterns),
|
||||
@@ -286,22 +284,6 @@ func (f *batchFetcher) fetchFiles(ctx context.Context, keys []gid.GID) (map[gid.
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (f *batchFetcher) fetchReports(ctx context.Context, keys []gid.GID) (map[gid.GID]*coredata.Report, error) {
|
||||
scope := coredata.NewScopeFromObjectID(keys[0])
|
||||
|
||||
reports, err := f.probo.Reports.GetByIDs(ctx, scope, keys...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot batch load reports: %w", err)
|
||||
}
|
||||
|
||||
result := make(map[gid.GID]*coredata.Report, len(reports))
|
||||
for _, v := range reports {
|
||||
result[v.ID] = v
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (f *batchFetcher) fetchCookieBanners(ctx context.Context, keys []gid.GID) (map[gid.GID]*coredata.CookieBanner, error) {
|
||||
scope := coredata.NewScopeFromObjectID(keys[0])
|
||||
|
||||
|
||||
@@ -151,8 +151,7 @@ type Audit implements Node {
|
||||
framework: Framework @goField(forceResolver: true)
|
||||
validFrom: Datetime
|
||||
validUntil: Datetime
|
||||
report: Report @goField(forceResolver: true)
|
||||
reportUrl: String @goField(forceResolver: true)
|
||||
reportFile: File @goField(forceResolver: true)
|
||||
state: AuditState!
|
||||
|
||||
controls(
|
||||
@@ -208,20 +207,6 @@ type Finding implements Node {
|
||||
permission(action: String!): Boolean! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type Report implements Node {
|
||||
id: ID!
|
||||
objectKey: String!
|
||||
mimeType: String!
|
||||
filename: String!
|
||||
size: Int!
|
||||
downloadUrl: String @goField(forceResolver: true)
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
audit: Audit @goField(forceResolver: true)
|
||||
|
||||
permission(action: String!): Boolean! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type AuditConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AuditConnection"
|
||||
|
||||
@@ -334,7 +334,8 @@ type TrustCenterDocumentAccess
|
||||
id: ID!
|
||||
status: TrustCenterDocumentAccessStatus!
|
||||
document: Document @goField(forceResolver: true)
|
||||
report: Report @goField(forceResolver: true)
|
||||
reportFile: File @goField(forceResolver: true)
|
||||
audit: Audit @goField(forceResolver: true)
|
||||
trustCenterFile: TrustCenterFile @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
|
||||
@@ -1056,24 +1056,55 @@ func (r *trustCenterDocumentAccessResolver) Document(ctx context.Context, obj *t
|
||||
return types.NewDocument(document), nil
|
||||
}
|
||||
|
||||
// Report is the resolver for the report field.
|
||||
func (r *trustCenterDocumentAccessResolver) Report(ctx context.Context, obj *types.TrustCenterDocumentAccess) (*types.Report, error) {
|
||||
scope, err := r.authorize(ctx, obj.TrustCenterAccessID, probo.ActionReportGet)
|
||||
// ReportFile is the resolver for the reportFile field.
|
||||
func (r *trustCenterDocumentAccessResolver) ReportFile(ctx context.Context, obj *types.TrustCenterDocumentAccess) (*types.File, error) {
|
||||
if _, err := r.authorize(ctx, obj.ID, probo.ActionFileGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if obj.ReportFile == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
loaders := dataloader.FromContext(ctx)
|
||||
|
||||
file, err := loaders.File.Load(ctx, obj.ReportFile.ID)
|
||||
if err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) {
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot load report file", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewFile(file), nil
|
||||
}
|
||||
|
||||
// Audit is the resolver for the audit field.
|
||||
func (r *trustCenterDocumentAccessResolver) Audit(ctx context.Context, obj *types.TrustCenterDocumentAccess) (*types.Audit, error) {
|
||||
scope, err := r.authorize(ctx, obj.ID, probo.ActionAuditGet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if obj.ReportID == nil {
|
||||
if obj.ReportFileID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
report, err := r.probo.Reports.Get(ctx, scope, *obj.ReportID)
|
||||
audit, err := r.probo.Audits.GetByReportFileID(ctx, scope, *obj.ReportFileID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot load report", log.Error(err))
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot load audit for report file", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewReport(report), nil
|
||||
return types.NewAudit(audit), nil
|
||||
}
|
||||
|
||||
// TrustCenterFile is the resolver for the trustCenterFile field.
|
||||
|
||||
@@ -77,9 +77,9 @@ func NewAudit(a *coredata.Audit) *Audit {
|
||||
UpdatedAt: a.UpdatedAt,
|
||||
}
|
||||
|
||||
if a.ReportID != nil {
|
||||
node.Report = &Report{
|
||||
ID: *a.ReportID,
|
||||
if a.ReportFileID != nil {
|
||||
node.ReportFile = &File{
|
||||
ID: *a.ReportFileID,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func NewReport(r *coredata.Report) *Report {
|
||||
return &Report{
|
||||
ID: r.ID,
|
||||
ObjectKey: r.ObjectKey,
|
||||
MimeType: r.MimeType,
|
||||
Filename: r.Filename,
|
||||
Size: int(r.Size),
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
}
|
||||
}
|
||||
@@ -42,13 +42,13 @@ type (
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
TrustCenterAccess *TrustCenterAccess `json:"trustCenterAccess"`
|
||||
Document *Document `json:"document,omitempty"`
|
||||
Report *Report `json:"report,omitempty"`
|
||||
ReportFile *File `json:"reportFile,omitempty"`
|
||||
TrustCenterFile *TrustCenterFile `json:"trustCenterFile,omitempty"`
|
||||
|
||||
// Internal fields used by resolvers
|
||||
TrustCenterAccessID gid.GID `json:"-"`
|
||||
DocumentID *gid.GID `json:"-"`
|
||||
ReportID *gid.GID `json:"-"`
|
||||
ReportFileID *gid.GID `json:"-"`
|
||||
TrustCenterFileID *gid.GID `json:"-"`
|
||||
}
|
||||
)
|
||||
@@ -62,7 +62,7 @@ func NewTrustCenterDocumentAccess(tcda *coredata.TrustCenterDocumentAccess) *Tru
|
||||
UpdatedAt: tcda.UpdatedAt,
|
||||
TrustCenterAccessID: tcda.TrustCenterAccessID,
|
||||
DocumentID: tcda.DocumentID,
|
||||
ReportID: tcda.ReportID,
|
||||
ReportFileID: tcda.ReportFileID,
|
||||
TrustCenterFileID: tcda.TrustCenterFileID,
|
||||
}
|
||||
|
||||
@@ -72,9 +72,9 @@ func NewTrustCenterDocumentAccess(tcda *coredata.TrustCenterDocumentAccess) *Tru
|
||||
}
|
||||
}
|
||||
|
||||
if tcda.ReportID != nil {
|
||||
object.Report = &Report{
|
||||
ID: *tcda.ReportID,
|
||||
if tcda.ReportFileID != nil {
|
||||
object.ReportFile = &File{
|
||||
ID: *tcda.ReportFileID,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1468,16 +1468,16 @@ func (r *Resolver) GetAuditTool(ctx context.Context, req *mcp.CallToolRequest, i
|
||||
return nil, types.GetAuditOutput{}, fmt.Errorf("cannot get audit: %w", err)
|
||||
}
|
||||
|
||||
var report *coredata.Report
|
||||
if audit.ReportID != nil {
|
||||
report, err = prb.Reports.Get(ctx, scope, *audit.ReportID)
|
||||
var file *coredata.File
|
||||
if audit.ReportFileID != nil {
|
||||
file, err = prb.Files.Get(ctx, scope, *audit.ReportFileID)
|
||||
if err != nil {
|
||||
return nil, types.GetAuditOutput{}, fmt.Errorf("cannot get audit report: %w", err)
|
||||
return nil, types.GetAuditOutput{}, fmt.Errorf("cannot get audit report file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, types.GetAuditOutput{
|
||||
Audit: types.NewAudit(audit, report),
|
||||
Audit: types.NewAudit(audit, file),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1532,16 +1532,16 @@ func (r *Resolver) UpdateAuditTool(ctx context.Context, req *mcp.CallToolRequest
|
||||
return nil, types.UpdateAuditOutput{}, fmt.Errorf("cannot update audit: %w", err)
|
||||
}
|
||||
|
||||
var report *coredata.Report
|
||||
if audit.ReportID != nil {
|
||||
report, err = svc.Reports.Get(ctx, scope, *audit.ReportID)
|
||||
var file *coredata.File
|
||||
if audit.ReportFileID != nil {
|
||||
file, err = svc.Files.Get(ctx, scope, *audit.ReportFileID)
|
||||
if err != nil {
|
||||
return nil, types.UpdateAuditOutput{}, fmt.Errorf("cannot get audit report: %w", err)
|
||||
return nil, types.UpdateAuditOutput{}, fmt.Errorf("cannot get audit report file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, types.UpdateAuditOutput{
|
||||
Audit: types.NewAudit(audit, report),
|
||||
Audit: types.NewAudit(audit, file),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
func NewAudit(a *coredata.Audit, report *coredata.Report) *Audit {
|
||||
func NewAudit(a *coredata.Audit, file *coredata.File) *Audit {
|
||||
audit := &Audit{
|
||||
ID: a.ID,
|
||||
Name: a.Name,
|
||||
@@ -27,16 +27,16 @@ func NewAudit(a *coredata.Audit, report *coredata.Report) *Audit {
|
||||
FrameworkID: a.FrameworkID,
|
||||
State: a.State,
|
||||
TrustCenterVisibility: a.TrustCenterVisibility,
|
||||
HasReport: a.ReportID != nil,
|
||||
HasReport: a.ReportFileID != nil,
|
||||
ValidFrom: a.ValidFrom,
|
||||
ValidUntil: a.ValidUntil,
|
||||
CreatedAt: a.CreatedAt,
|
||||
UpdatedAt: a.UpdatedAt,
|
||||
}
|
||||
|
||||
if report != nil {
|
||||
audit.ReportFilename = &report.Filename
|
||||
audit.ReportMimeType = &report.MimeType
|
||||
if file != nil {
|
||||
audit.ReportFilename = &file.FileName
|
||||
audit.ReportMimeType = &file.MimeType
|
||||
}
|
||||
|
||||
return audit
|
||||
|
||||
@@ -227,7 +227,7 @@ func SlackHandler(slackSvc *slack.Service, slackSigningSecret string, logger *lo
|
||||
switch gID.EntityType() {
|
||||
case coredata.DocumentEntityType:
|
||||
documentIDs = []gid.GID{gID}
|
||||
case coredata.ReportEntityType:
|
||||
case coredata.FileEntityType:
|
||||
reportIDs = []gid.GID{gID}
|
||||
case coredata.TrustCenterFileEntityType:
|
||||
fileIDs = []gid.GID{gID}
|
||||
|
||||
@@ -83,21 +83,21 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
||||
|
||||
return types.NewFramework(framework), nil
|
||||
|
||||
case coredata.ReportEntityType:
|
||||
case coredata.FileEntityType:
|
||||
trustCenter := compliancepage.CompliancePageFromContext(ctx)
|
||||
|
||||
report, err := trustService.Reports.Get(ctx, scope, trustCenter.OrganizationID, id)
|
||||
file, err := trustService.Reports.Get(ctx, scope, trustCenter.OrganizationID, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, trust.ErrReportNotFound) || errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return nil, gqlutils.NotFoundf(ctx, "node %q not found", id)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot get report", log.Error(err))
|
||||
r.logger.ErrorCtx(ctx, "cannot get audit report file", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewReport(report), nil
|
||||
return types.NewAuditReport(file), nil
|
||||
|
||||
case coredata.AuditEntityType:
|
||||
audit, err := trustService.Audits.Get(ctx, scope, id)
|
||||
|
||||
@@ -114,9 +114,9 @@ type Framework implements Node @nda {
|
||||
darkLogoURL: String @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type Report implements Node @nda {
|
||||
type AuditReport implements Node @nda {
|
||||
id: ID!
|
||||
filename: String!
|
||||
fileName: String!
|
||||
isUserAuthorized: Boolean! @goField(forceResolver: true)
|
||||
access: DocumentAccess @goField(forceResolver: true)
|
||||
}
|
||||
@@ -125,7 +125,7 @@ type Audit implements Node @nda {
|
||||
id: ID!
|
||||
name: String
|
||||
framework: Framework! @goField(forceResolver: true)
|
||||
report: Report @goField(forceResolver: true)
|
||||
reportFile: AuditReport @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type AuditConnection @nda {
|
||||
|
||||
@@ -44,8 +44,8 @@ func (r *auditResolver) Framework(ctx context.Context, obj *types.Audit) (*types
|
||||
return types.NewFramework(framework), nil
|
||||
}
|
||||
|
||||
// Report is the resolver for the report field.
|
||||
func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Report, error) {
|
||||
// ReportFile is the resolver for the reportFile field.
|
||||
func (r *auditResolver) ReportFile(ctx context.Context, obj *types.Audit) (*types.AuditReport, error) {
|
||||
scope := coredata.NewScopeFromObjectID(obj.ID)
|
||||
trustService := r.trust
|
||||
|
||||
@@ -55,19 +55,105 @@ func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Re
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
if audit.ReportID == nil {
|
||||
if audit.ReportFileID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
trustCenter := compliancepage.CompliancePageFromContext(ctx)
|
||||
|
||||
report, err := trustService.Reports.Get(ctx, scope, trustCenter.OrganizationID, *audit.ReportID)
|
||||
file, err := trustService.Reports.Get(ctx, scope, trustCenter.OrganizationID, *audit.ReportFileID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot load report", log.Error(err))
|
||||
r.logger.ErrorCtx(ctx, "cannot load report file", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewReport(report), nil
|
||||
return types.NewAuditReport(file), nil
|
||||
}
|
||||
|
||||
// IsUserAuthorized is the resolver for the isUserAuthorized field.
|
||||
func (r *auditReportResolver) IsUserAuthorized(ctx context.Context, obj *types.AuditReport) (bool, error) {
|
||||
scope := coredata.NewScopeFromObjectID(obj.ID)
|
||||
trustService := r.trust
|
||||
trustCenter := compliancepage.CompliancePageFromContext(ctx)
|
||||
|
||||
audit, err := trustService.Audits.GetByReportFileID(ctx, scope, obj.ID)
|
||||
if err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot load audit for report file", log.Error(err))
|
||||
|
||||
return false, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
if audit.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
if identity == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
reportAccess, err := trustService.TrustCenterAccesses.GetReportFileAccess(ctx, scope,
|
||||
trustCenter.ID,
|
||||
identity.ID,
|
||||
obj.ID,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, trust.ErrMembershipNotFound) ||
|
||||
errors.Is(err, trust.ErrUserNotFound) ||
|
||||
errors.Is(err, trust.ErrUserInactive) ||
|
||||
errors.Is(err, trust.ErrDocumentAccessNotFound) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot check report access", log.Error(err))
|
||||
|
||||
return false, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return reportAccess.Status == coredata.TrustCenterDocumentAccessStatusGranted, nil
|
||||
}
|
||||
|
||||
// Access is the resolver for the access field.
|
||||
func (r *auditReportResolver) Access(ctx context.Context, obj *types.AuditReport) (*types.DocumentAccess, error) {
|
||||
scope := coredata.NewScopeFromObjectID(obj.ID)
|
||||
trustService := r.trust
|
||||
trustCenter := compliancepage.CompliancePageFromContext(ctx)
|
||||
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
if identity == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
access, err := trustService.TrustCenterAccesses.GetReportFileAccess(
|
||||
ctx, scope,
|
||||
trustCenter.ID,
|
||||
identity.ID,
|
||||
obj.ID,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, trust.ErrMembershipNotFound) ||
|
||||
errors.Is(err, trust.ErrUserNotFound) ||
|
||||
errors.Is(err, trust.ErrDocumentAccessNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if errors.Is(err, trust.ErrUserInactive) {
|
||||
return nil, gqlutils.Forbidden(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot get audit report access", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.DocumentAccess{
|
||||
ID: access.ID,
|
||||
Status: access.Status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Framework is the resolver for the framework field on ComplianceFramework.
|
||||
@@ -294,7 +380,7 @@ func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.Expo
|
||||
trustService := r.trust
|
||||
trustCenter := compliancepage.CompliancePageFromContext(ctx)
|
||||
|
||||
audit, err := trustService.Audits.GetByReportID(ctx, scope, input.ReportID)
|
||||
audit, err := trustService.Audits.GetByReportFileID(ctx, scope, input.ReportID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot load audit", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
@@ -317,7 +403,7 @@ func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.Expo
|
||||
return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated")
|
||||
}
|
||||
|
||||
reportAccess, err := trustService.TrustCenterAccesses.GetReportAccess(
|
||||
reportAccess, err := trustService.TrustCenterAccesses.GetReportFileAccess(
|
||||
ctx, scope,
|
||||
trustCenter.ID,
|
||||
identity.ID,
|
||||
@@ -458,7 +544,7 @@ func (r *mutationResolver) RequestReportAccess(ctx context.Context, input types.
|
||||
scope := coredata.NewScopeFromObjectID(trustCenter.ID)
|
||||
trustService := r.trust
|
||||
|
||||
audit, err := trustService.Audits.GetByReportID(ctx, scope, input.ReportID)
|
||||
audit, err := trustService.Audits.GetByReportFileID(ctx, scope, input.ReportID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot load audit", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
@@ -543,87 +629,6 @@ func (r *mutationResolver) RequestTrustCenterFileAccess(ctx context.Context, inp
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsUserAuthorized is the resolver for the isUserAuthorized field.
|
||||
func (r *reportResolver) IsUserAuthorized(ctx context.Context, obj *types.Report) (bool, error) {
|
||||
scope := coredata.NewScopeFromObjectID(obj.ID)
|
||||
trustService := r.trust
|
||||
trustCenter := compliancepage.CompliancePageFromContext(ctx)
|
||||
|
||||
audit, err := trustService.Audits.GetByReportID(ctx, scope, obj.ID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot load document", log.Error(err))
|
||||
return false, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
if audit.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
if identity == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
reportAccess, err := trustService.TrustCenterAccesses.GetReportAccess(ctx, scope,
|
||||
trustCenter.ID,
|
||||
identity.ID,
|
||||
obj.ID,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, trust.ErrMembershipNotFound) ||
|
||||
errors.Is(err, trust.ErrUserNotFound) ||
|
||||
errors.Is(err, trust.ErrUserInactive) ||
|
||||
errors.Is(err, trust.ErrDocumentAccessNotFound) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot check report access", log.Error(err))
|
||||
|
||||
return false, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return reportAccess.Status == coredata.TrustCenterDocumentAccessStatusGranted, nil
|
||||
}
|
||||
|
||||
// Access is the resolver for the access field.
|
||||
func (r *reportResolver) Access(ctx context.Context, obj *types.Report) (*types.DocumentAccess, error) {
|
||||
scope := coredata.NewScopeFromObjectID(obj.ID)
|
||||
trustService := r.trust
|
||||
trustCenter := compliancepage.CompliancePageFromContext(ctx)
|
||||
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
if identity == nil {
|
||||
return nil, nil // User is not authenticated, so no access requested
|
||||
}
|
||||
|
||||
access, err := trustService.TrustCenterAccesses.GetReportAccess(
|
||||
ctx, scope,
|
||||
trustCenter.ID,
|
||||
identity.ID,
|
||||
obj.ID,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, trust.ErrMembershipNotFound) ||
|
||||
errors.Is(err, trust.ErrUserNotFound) ||
|
||||
errors.Is(err, trust.ErrDocumentAccessNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if errors.Is(err, trust.ErrUserInactive) {
|
||||
return nil, gqlutils.Forbidden(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot get audit report access", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.DocumentAccess{
|
||||
ID: access.ID,
|
||||
Status: access.Status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the totalCount field.
|
||||
func (r *subprocessorConnectionResolver) TotalCount(ctx context.Context, obj *types.SubprocessorConnection) (int, error) {
|
||||
scope := coredata.NewScopeFromObjectID(obj.ParentID)
|
||||
@@ -987,6 +992,9 @@ func (r *trustCenterReferenceResolver) LogoURL(ctx context.Context, obj *types.T
|
||||
// Audit returns schema.AuditResolver implementation.
|
||||
func (r *Resolver) Audit() schema.AuditResolver { return &auditResolver{r} }
|
||||
|
||||
// AuditReport returns schema.AuditReportResolver implementation.
|
||||
func (r *Resolver) AuditReport() schema.AuditReportResolver { return &auditReportResolver{r} }
|
||||
|
||||
// ComplianceFramework returns schema.ComplianceFrameworkResolver implementation.
|
||||
func (r *Resolver) ComplianceFramework() schema.ComplianceFrameworkResolver {
|
||||
return &complianceFrameworkResolver{r}
|
||||
@@ -998,9 +1006,6 @@ func (r *Resolver) Document() schema.DocumentResolver { return &documentResolver
|
||||
// Framework returns schema.FrameworkResolver implementation.
|
||||
func (r *Resolver) Framework() schema.FrameworkResolver { return &frameworkResolver{r} }
|
||||
|
||||
// Report returns schema.ReportResolver implementation.
|
||||
func (r *Resolver) Report() schema.ReportResolver { return &reportResolver{r} }
|
||||
|
||||
// SubprocessorConnection returns schema.SubprocessorConnectionResolver implementation.
|
||||
func (r *Resolver) SubprocessorConnection() schema.SubprocessorConnectionResolver {
|
||||
return &subprocessorConnectionResolver{r}
|
||||
@@ -1020,10 +1025,10 @@ func (r *Resolver) TrustCenterReference() schema.TrustCenterReferenceResolver {
|
||||
}
|
||||
|
||||
type auditResolver struct{ *Resolver }
|
||||
type auditReportResolver struct{ *Resolver }
|
||||
type complianceFrameworkResolver struct{ *Resolver }
|
||||
type documentResolver struct{ *Resolver }
|
||||
type frameworkResolver struct{ *Resolver }
|
||||
type reportResolver struct{ *Resolver }
|
||||
type subprocessorConnectionResolver struct{ *Resolver }
|
||||
type trustCenterResolver struct{ *Resolver }
|
||||
type trustCenterFileResolver struct{ *Resolver }
|
||||
|
||||
@@ -18,9 +18,9 @@ import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func NewReport(r *coredata.Report) *Report {
|
||||
return &Report{
|
||||
ID: r.ID,
|
||||
Filename: r.Filename,
|
||||
func NewAuditReport(f *coredata.File) *AuditReport {
|
||||
return &AuditReport{
|
||||
ID: f.ID,
|
||||
FileName: f.FileName,
|
||||
}
|
||||
}
|
||||
@@ -349,14 +349,9 @@ func (s *Service) loadDocumentsReportsAndFilesFromAccesses(
|
||||
)
|
||||
}
|
||||
|
||||
if access.ReportID != nil {
|
||||
rep := &coredata.Report{}
|
||||
if err := rep.LoadByID(ctx, conn, scope, *access.ReportID); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("cannot load report: %w", err)
|
||||
}
|
||||
|
||||
if access.ReportFileID != nil {
|
||||
audit := &coredata.Audit{}
|
||||
if err := audit.LoadByReportID(ctx, conn, scope, *access.ReportID); err != nil {
|
||||
if err := audit.LoadByReportFileID(ctx, conn, scope, *access.ReportFileID); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
|
||||
@@ -373,7 +368,7 @@ func (s *Service) loadDocumentsReportsAndFilesFromAccesses(
|
||||
reports = append(
|
||||
reports,
|
||||
SlackMessageReport{
|
||||
ID: access.ReportID.String(),
|
||||
ID: access.ReportFileID.String(),
|
||||
Title: label,
|
||||
AuditID: audit.ID.String(),
|
||||
Status: access.Status.String(),
|
||||
|
||||
@@ -53,18 +53,17 @@ func (s AuditService) Get(
|
||||
return audit, nil
|
||||
}
|
||||
|
||||
func (s AuditService) GetByReportID(
|
||||
func (s AuditService) GetByReportFileID(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
reportID gid.GID,
|
||||
fileID gid.GID,
|
||||
) (*coredata.Audit, error) {
|
||||
audit := &coredata.Audit{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
err := audit.LoadByReportID(ctx, conn, scope, reportID)
|
||||
if err != nil {
|
||||
if err := audit.LoadByReportFileID(ctx, conn, scope, fileID); err != nil {
|
||||
return fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ package trust
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
@@ -36,33 +37,43 @@ func (s ReportService) Get(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
organizationID gid.GID,
|
||||
reportID gid.GID,
|
||||
) (*coredata.Report, error) {
|
||||
report, err := s.loadByID(ctx, scope, reportID)
|
||||
fileID gid.GID,
|
||||
) (*coredata.File, error) {
|
||||
file, err := s.loadByID(ctx, scope, fileID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if report.OrganizationID != organizationID {
|
||||
if file.OrganizationID != organizationID {
|
||||
return nil, ErrReportNotFound
|
||||
}
|
||||
|
||||
return report, nil
|
||||
// check the given report file ID is linked to an audit in order to avoid
|
||||
// being able to get any file from the report request.
|
||||
_, err = s.svc.Audits.GetByReportFileID(ctx, scope, fileID)
|
||||
if err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return nil, ErrReportNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("cannot verify report file: %w", err)
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func (s ReportService) loadByID(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
reportID gid.GID,
|
||||
) (*coredata.Report, error) {
|
||||
report := &coredata.Report{}
|
||||
fileID gid.GID,
|
||||
) (*coredata.File, error) {
|
||||
file := &coredata.File{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
err := report.LoadByID(ctx, conn, scope, reportID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load report: %w", err)
|
||||
if err := file.LoadActiveByID(ctx, conn, scope, fileID); err != nil {
|
||||
return fmt.Errorf("cannot load file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -72,28 +83,28 @@ func (s ReportService) loadByID(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return report, nil
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func (s ReportService) GenerateDownloadURL(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
reportID gid.GID,
|
||||
fileID gid.GID,
|
||||
expiresIn time.Duration,
|
||||
) (*string, error) {
|
||||
report, err := s.loadByID(ctx, scope, reportID)
|
||||
file, err := s.loadByID(ctx, scope, fileID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get report: %w", err)
|
||||
return nil, fmt.Errorf("cannot get file: %w", err)
|
||||
}
|
||||
|
||||
presignClient := s3.NewPresignClient(s.svc.s3)
|
||||
|
||||
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: new(s.svc.bucket),
|
||||
Key: new(report.ObjectKey),
|
||||
Key: new(file.FileKey),
|
||||
ResponseCacheControl: new("max-age=3600, public"),
|
||||
ResponseContentType: new(report.MimeType),
|
||||
ResponseContentDisposition: new(fmt.Sprintf("attachment; filename=\"%s\"", report.Filename)),
|
||||
ResponseContentType: new(file.MimeType),
|
||||
ResponseContentDisposition: new(fmt.Sprintf("attachment; filename=\"%s\"", file.FileName)),
|
||||
}, func(opts *s3.PresignOptions) {
|
||||
opts.Expires = expiresIn
|
||||
})
|
||||
@@ -134,16 +145,16 @@ func (s ReportService) ExportPDFWithoutWatermark(
|
||||
func (s ReportService) exportPDFData(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
reportID gid.GID,
|
||||
fileID gid.GID,
|
||||
) ([]byte, error) {
|
||||
report, err := s.loadByID(ctx, scope, reportID)
|
||||
file, err := s.loadByID(ctx, scope, fileID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get report: %w", err)
|
||||
return nil, fmt.Errorf("cannot get file: %w", err)
|
||||
}
|
||||
|
||||
result, err := s.svc.s3.GetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: new(s.svc.bucket),
|
||||
Key: new(report.ObjectKey),
|
||||
Key: new(file.FileKey),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot download PDF from S3: %w", err)
|
||||
|
||||
@@ -100,8 +100,8 @@ func (s TrustCenterAccessService) Request(
|
||||
}
|
||||
|
||||
for _, audit := range allAudits {
|
||||
if audit.ReportID != nil {
|
||||
reportIDs = append(reportIDs, *audit.ReportID)
|
||||
if audit.ReportFileID != nil {
|
||||
reportIDs = append(reportIDs, *audit.ReportFileID)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,7 @@ func (s TrustCenterAccessService) Request(
|
||||
return fmt.Errorf("cannot bulk insert trust center document accesses: %w", err)
|
||||
}
|
||||
|
||||
if err := accesses.BulkInsertReportAccesses(
|
||||
if err := accesses.BulkInsertReportFileAccesses(
|
||||
ctx,
|
||||
tx,
|
||||
scope,
|
||||
@@ -255,12 +255,12 @@ func (s TrustCenterAccessService) GetDocumentAccess(
|
||||
return documentAccess, nil
|
||||
}
|
||||
|
||||
func (s TrustCenterAccessService) GetReportAccess(
|
||||
func (s TrustCenterAccessService) GetReportFileAccess(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
trustCenterID gid.GID,
|
||||
identityID gid.GID,
|
||||
reportID gid.GID,
|
||||
reportFileID gid.GID,
|
||||
) (*coredata.TrustCenterDocumentAccess, error) {
|
||||
var reportAccess *coredata.TrustCenterDocumentAccess
|
||||
|
||||
@@ -289,7 +289,7 @@ func (s TrustCenterAccessService) GetReportAccess(
|
||||
|
||||
reportAccess = &coredata.TrustCenterDocumentAccess{}
|
||||
|
||||
err = reportAccess.LoadByTrustCenterAccessIDAndReportID(ctx, conn, scope, access.ID, reportID)
|
||||
err = reportAccess.LoadByTrustCenterAccessIDAndReportFileID(ctx, conn, scope, access.ID, reportFileID)
|
||||
if err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return ErrDocumentAccessNotFound
|
||||
@@ -405,7 +405,7 @@ func (s *TrustCenterAccessService) GrantByIDs(
|
||||
}
|
||||
|
||||
if len(reportIDs) > 0 {
|
||||
if err := coredata.GrantByReportIDs(ctx, tx, scope, access.ID, reportIDs, now); err != nil {
|
||||
if err := coredata.GrantByReportFileIDs(ctx, tx, scope, access.ID, reportIDs, now); err != nil {
|
||||
return fmt.Errorf("cannot grant report accesses: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -526,7 +526,7 @@ func (s *TrustCenterAccessService) RejectOrRevokeByIDs(
|
||||
if len(reportIDs) > 0 {
|
||||
shouldSendEmail = true
|
||||
|
||||
if err := coredata.RejectOrRevokeByReportIDs(ctx, tx, scope, access.ID, reportIDs, now); err != nil {
|
||||
if err := coredata.RejectOrRevokeByReportFileIDs(ctx, tx, scope, access.ID, reportIDs, now); err != nil {
|
||||
return fmt.Errorf("cannot reject/revoke report accesses: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -645,8 +645,8 @@ func extractExistingIDs(accesses coredata.TrustCenterDocumentAccesses) ([]gid.GI
|
||||
documentIDs = append(documentIDs, *access.DocumentID)
|
||||
}
|
||||
|
||||
if access.ReportID != nil {
|
||||
reportIDs = append(reportIDs, *access.ReportID)
|
||||
if access.ReportFileID != nil {
|
||||
reportIDs = append(reportIDs, *access.ReportFileID)
|
||||
}
|
||||
|
||||
if access.TrustCenterFileID != nil {
|
||||
@@ -678,36 +678,36 @@ func reportAccessLabels(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope coredata.Scoper,
|
||||
reportIDs []gid.GID,
|
||||
reportFileIDs []gid.GID,
|
||||
) ([]string, error) {
|
||||
var reports coredata.Reports
|
||||
if err := reports.LoadByIDs(ctx, conn, scope, reportIDs); err != nil {
|
||||
return nil, fmt.Errorf("cannot load reports by IDs: %w", err)
|
||||
var reportFiles coredata.Files
|
||||
if err := reportFiles.LoadByIDs(ctx, conn, scope, reportFileIDs); err != nil {
|
||||
return nil, fmt.Errorf("cannot load report files by IDs: %w", err)
|
||||
}
|
||||
|
||||
reportByID := make(map[gid.GID]*coredata.Report, len(reports))
|
||||
for _, report := range reports {
|
||||
reportByID[report.ID] = report
|
||||
fileByID := make(map[gid.GID]*coredata.File, len(reportFiles))
|
||||
for _, f := range reportFiles {
|
||||
fileByID[f.ID] = f
|
||||
}
|
||||
|
||||
var audits coredata.Audits
|
||||
if err := audits.LoadByReportIDs(ctx, conn, scope, reportIDs); err != nil {
|
||||
return nil, fmt.Errorf("cannot load audits by report IDs: %w", err)
|
||||
if err := audits.LoadByReportFileIDs(ctx, conn, scope, reportFileIDs); err != nil {
|
||||
return nil, fmt.Errorf("cannot load audits by report file IDs: %w", err)
|
||||
}
|
||||
|
||||
auditByReportID := make(map[gid.GID]*coredata.Audit, len(audits))
|
||||
auditByFileID := make(map[gid.GID]*coredata.Audit, len(audits))
|
||||
frameworkIDSet := make(map[gid.GID]struct{})
|
||||
|
||||
for _, audit := range audits {
|
||||
if audit.ReportID == nil {
|
||||
if audit.ReportFileID == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := auditByReportID[*audit.ReportID]; exists {
|
||||
if _, exists := auditByFileID[*audit.ReportFileID]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
auditByReportID[*audit.ReportID] = audit
|
||||
auditByFileID[*audit.ReportFileID] = audit
|
||||
frameworkIDSet[audit.FrameworkID] = struct{}{}
|
||||
}
|
||||
|
||||
@@ -729,23 +729,23 @@ func reportAccessLabels(
|
||||
}
|
||||
}
|
||||
|
||||
labels := make([]string, 0, len(reportIDs))
|
||||
labels := make([]string, 0, len(reportFileIDs))
|
||||
|
||||
for _, reportID := range reportIDs {
|
||||
report, ok := reportByID[reportID]
|
||||
for _, fileID := range reportFileIDs {
|
||||
file, ok := fileByID[fileID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot load report %q: %w", reportID, coredata.ErrResourceNotFound)
|
||||
return nil, fmt.Errorf("cannot load report file %q: %w", fileID, coredata.ErrResourceNotFound)
|
||||
}
|
||||
|
||||
audit, ok := auditByReportID[reportID]
|
||||
audit, ok := auditByFileID[fileID]
|
||||
if !ok {
|
||||
labels = append(labels, report.Filename)
|
||||
labels = append(labels, file.FileName)
|
||||
continue
|
||||
}
|
||||
|
||||
framework, ok := frameworkByID[audit.FrameworkID]
|
||||
if !ok {
|
||||
labels = append(labels, report.Filename)
|
||||
labels = append(labels, file.FileName)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user