From 8231aecaba713644fa1903ffbc759d01750bc72d Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Mon, 1 Jun 2026 19:41:38 +0200 Subject: [PATCH] Clarify trust center access rejection emails Rejecting one audit report via Slack could look like a blanket denial when HIPAA and SOC 2 reports shared a filename. Use framework and audit name in rejection emails. Signed-off-by: Sacha Al Himdani --- pkg/coredata/audit.go | 46 +++++++++ .../connector_oauth_client_metadata_test.go | 1 + pkg/trust/trust_center_access_service.go | 96 +++++++++++++++++-- 3 files changed, 137 insertions(+), 6 deletions(-) diff --git a/pkg/coredata/audit.go b/pkg/coredata/audit.go index 1ede4c2c3..a98606aa1 100644 --- a/pkg/coredata/audit.go +++ b/pkg/coredata/audit.go @@ -680,3 +680,49 @@ LIMIT 1; return nil } + +func (as *Audits) LoadByReportIDs( + ctx context.Context, + conn pg.Querier, + scope Scoper, + reportIDs []gid.GID, +) error { + q := ` +SELECT + id, + name, + organization_id, + framework_id, + report_id, + valid_from, + valid_until, + state, + trust_center_visibility, + created_at, + updated_at +FROM + audits +WHERE + %s + AND report_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 audits by report IDs: %w", err) + } + + audits, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Audit]) + if err != nil { + return fmt.Errorf("cannot collect audits by report IDs: %w", err) + } + + *as = audits + + return nil +} diff --git a/pkg/server/api/console/v1/connector_oauth_client_metadata_test.go b/pkg/server/api/console/v1/connector_oauth_client_metadata_test.go index 09aba4f5d..df8c2fcf1 100644 --- a/pkg/server/api/console/v1/connector_oauth_client_metadata_test.go +++ b/pkg/server/api/console/v1/connector_oauth_client_metadata_test.go @@ -42,6 +42,7 @@ func TestHandleConnectorOAuthClientMetadata(t *testing.T) { ) res := rec.Result() + defer func() { _ = res.Body.Close() }() assert.Equal(t, http.StatusOK, res.StatusCode) diff --git a/pkg/trust/trust_center_access_service.go b/pkg/trust/trust_center_access_service.go index d1bd06530..24f1f4fe0 100644 --- a/pkg/trust/trust_center_access_service.go +++ b/pkg/trust/trust_center_access_service.go @@ -579,15 +579,13 @@ func (s *TrustCenterAccessService) sendDocumentAccessRejectedEmail( } } - var reports coredata.Reports if len(reportIDs) > 0 { - if err := reports.LoadByIDs(ctx, tx, scope, reportIDs); err != nil { - return fmt.Errorf("cannot load reports by IDs: %w", err) + reportLabels, err := reportAccessLabels(ctx, tx, scope, reportIDs) + if err != nil { + return fmt.Errorf("cannot build report access labels: %w", err) } - for _, r := range reports { - fileNames = append(fileNames, r.Filename) - } + fileNames = append(fileNames, reportLabels...) } var files coredata.TrustCenterFiles @@ -675,3 +673,89 @@ func filterExistingIDs(allIDs []gid.GID, existingIDs []gid.GID) []gid.GID { return newIDs } + +func reportAccessLabels( + ctx context.Context, + conn pg.Querier, + scope coredata.Scoper, + reportIDs []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) + } + + reportByID := make(map[gid.GID]*coredata.Report, len(reports)) + for _, report := range reports { + reportByID[report.ID] = report + } + + 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) + } + + auditByReportID := make(map[gid.GID]*coredata.Audit, len(audits)) + frameworkIDSet := make(map[gid.GID]struct{}) + + for _, audit := range audits { + if audit.ReportID == nil { + continue + } + + if _, exists := auditByReportID[*audit.ReportID]; exists { + continue + } + + auditByReportID[*audit.ReportID] = audit + frameworkIDSet[audit.FrameworkID] = struct{}{} + } + + frameworkIDs := make([]gid.GID, 0, len(frameworkIDSet)) + for frameworkID := range frameworkIDSet { + frameworkIDs = append(frameworkIDs, frameworkID) + } + + frameworkByID := make(map[gid.GID]*coredata.Framework, len(frameworkIDs)) + + if len(frameworkIDs) > 0 { + var frameworks coredata.Frameworks + if err := frameworks.LoadByIDs(ctx, conn, scope, frameworkIDs); err != nil { + return nil, fmt.Errorf("cannot load frameworks by IDs: %w", err) + } + + for _, framework := range frameworks { + frameworkByID[framework.ID] = framework + } + } + + labels := make([]string, 0, len(reportIDs)) + + for _, reportID := range reportIDs { + report, ok := reportByID[reportID] + if !ok { + return nil, fmt.Errorf("cannot load report %q: %w", reportID, coredata.ErrResourceNotFound) + } + + audit, ok := auditByReportID[reportID] + if !ok { + labels = append(labels, report.Filename) + continue + } + + framework, ok := frameworkByID[audit.FrameworkID] + if !ok { + labels = append(labels, report.Filename) + continue + } + + if audit.Name != nil && *audit.Name != "" { + labels = append(labels, fmt.Sprintf("%s - %s", framework.Name, *audit.Name)) + continue + } + + labels = append(labels, framework.Name) + } + + return labels, nil +}