update
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWriteAuditWorkbookCreatesSafeExcelFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
location, err := time.LoadLocation("America/Chicago")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
createdAt := time.Date(2026, time.July, 28, 20, 10, 32, 0, location)
|
||||
records := []auditRecord{{
|
||||
EventType: "code_exchange_failed",
|
||||
CustomerLabel: "Acme & Sons",
|
||||
User: "taiadmin",
|
||||
Hostname: "=HYPERLINK(\"https://invalid.example\")",
|
||||
PackageSlug: "sentinelone-linux",
|
||||
SourceIP: "203.0.113.10",
|
||||
Details: "reason=code_not_found code_hint=ABCDE",
|
||||
CreatedAt: createdAt,
|
||||
}}
|
||||
|
||||
var output bytes.Buffer
|
||||
if err := writeAuditWorkbook(
|
||||
&output,
|
||||
records,
|
||||
auditFilters{TimeRange: "all"},
|
||||
createdAt,
|
||||
location,
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.HasPrefix(output.Bytes(), []byte("PK")) {
|
||||
t.Fatal("workbook is not a ZIP-based Excel file")
|
||||
}
|
||||
|
||||
archive, err := zip.NewReader(bytes.NewReader(output.Bytes()), int64(output.Len()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
required := map[string]bool{
|
||||
"[Content_Types].xml": false,
|
||||
"xl/workbook.xml": false,
|
||||
"xl/styles.xml": false,
|
||||
"xl/worksheets/sheet1.xml": false,
|
||||
"xl/_rels/workbook.xml.rels": false,
|
||||
}
|
||||
var worksheet string
|
||||
for _, file := range archive.File {
|
||||
if _, ok := required[file.Name]; ok {
|
||||
required[file.Name] = true
|
||||
}
|
||||
if file.Name != "xl/worksheets/sheet1.xml" {
|
||||
continue
|
||||
}
|
||||
reader, err := file.Open()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
content, err := io.ReadAll(reader)
|
||||
_ = reader.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
worksheet = string(content)
|
||||
}
|
||||
for file, found := range required {
|
||||
if !found {
|
||||
t.Errorf("workbook is missing %s", file)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(worksheet, "Acme & Sons") {
|
||||
t.Fatal("worksheet text was not XML escaped")
|
||||
}
|
||||
if strings.Contains(worksheet, "<f>") {
|
||||
t.Fatal("untrusted audit text must not be written as a formula")
|
||||
}
|
||||
if !strings.Contains(worksheet, `t="inlineStr"`) ||
|
||||
!strings.Contains(worksheet, `=HYPERLINK`) {
|
||||
t.Fatal("formula-looking audit text was not preserved as a text cell")
|
||||
}
|
||||
|
||||
if samplePath := os.Getenv("TAPM_AUDIT_SAMPLE_PATH"); samplePath != "" {
|
||||
if err := os.WriteFile(samplePath, output.Bytes(), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuditExportReturnsAllRowsAndRecordsDownload(t *testing.T) {
|
||||
server, sessionToken := newAuthorizationTestServer(t)
|
||||
server.cfg.DisplayTimeZone = time.UTC
|
||||
for _, eventType := range []string{"older_event", "newer_event"} {
|
||||
if _, err := server.db.Exec(
|
||||
`INSERT INTO audit_events (event_type, details) VALUES (?, '')`,
|
||||
eventType,
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/portal/audit/export?audit_range=all", nil)
|
||||
request.AddCookie(&http.Cookie{Name: sessionCookieName, Value: sessionToken})
|
||||
response := httptest.NewRecorder()
|
||||
server.handleAuditExport(response, request)
|
||||
|
||||
if response.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, body = %s", response.Code, response.Body.String())
|
||||
}
|
||||
if contentType := response.Header().Get("Content-Type"); contentType !=
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" {
|
||||
t.Fatalf("content type = %q", contentType)
|
||||
}
|
||||
if disposition := response.Header().Get("Content-Disposition"); !strings.Contains(
|
||||
disposition,
|
||||
`attachment; filename="tapm-audit-`,
|
||||
) {
|
||||
t.Fatalf("content disposition = %q", disposition)
|
||||
}
|
||||
|
||||
archive, err := zip.NewReader(
|
||||
bytes.NewReader(response.Body.Bytes()),
|
||||
int64(response.Body.Len()),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var worksheet string
|
||||
for _, file := range archive.File {
|
||||
if file.Name != "xl/worksheets/sheet1.xml" {
|
||||
continue
|
||||
}
|
||||
reader, err := file.Open()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
content, err := io.ReadAll(reader)
|
||||
_ = reader.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
worksheet = string(content)
|
||||
}
|
||||
if !strings.Contains(worksheet, "older_event") ||
|
||||
!strings.Contains(worksheet, "newer_event") {
|
||||
t.Fatal("complete export did not contain all retained audit rows")
|
||||
}
|
||||
|
||||
var exportEvents int
|
||||
if err := server.db.QueryRow(
|
||||
`SELECT COUNT(*) FROM audit_events WHERE event_type = 'audit_exported'`,
|
||||
).Scan(&exportEvents); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if exportEvents != 1 {
|
||||
t.Fatalf("audit_exported events = %d, want 1", exportEvents)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuditExportRequiresTechnicianSession(t *testing.T) {
|
||||
server, _ := newAuthorizationTestServer(t)
|
||||
request := httptest.NewRequest(http.MethodGet, "/portal/audit/export?audit_range=all", nil)
|
||||
response := httptest.NewRecorder()
|
||||
server.Routes().ServeHTTP(response, request)
|
||||
if response.Code != http.StatusSeeOther {
|
||||
t.Fatalf("status = %d, want redirect", response.Code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user