From 5808e90e5a523e048e3d2fc75951dbe0630d8a8a Mon Sep 17 00:00:00 2001 From: miyuko Date: Sun, 3 May 2026 04:46:42 +0100 Subject: [PATCH] Allow detaching all audit records related to a site. --- src/auth.go | 17 +++++++++++++---- src/main.go | 54 ++++++++++++++++++++++++++++++++++++---------------- src/pages.go | 2 +- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/auth.go b/src/auth.go index 067ae31..b178eb2 100644 --- a/src/auth.go +++ b/src/auth.go @@ -78,16 +78,25 @@ func GetHost(r *http.Request) (string, error) { return host, nil } -func IsValidProjectName(name string) bool { - return !strings.HasPrefix(name, ".") && !strings.Contains(name, "%") +func ValidateProjectName(name string) error { + if strings.HasPrefix(name, ".") { + return fmt.Errorf("must not start with %q", ".") + } + + forbiddenChars := "%*" + if strings.ContainsAny(name, forbiddenChars) { + return fmt.Errorf("must not contain any of %q", forbiddenChars) + } + + return nil } func GetProjectName(r *http.Request) (string, error) { // path must be either `/` or `/foo/` (`/foo` is accepted as an alias) path := strings.TrimPrefix(strings.TrimSuffix(r.URL.Path, "/"), "/") - if !IsValidProjectName(path) { + if err := ValidateProjectName(path); err != nil { return "", AuthError{http.StatusBadRequest, - fmt.Sprintf("directory name %q is reserved", ".index")} + fmt.Sprintf("directory name: %v", err)} } else if strings.Contains(path, "/") { return "", AuthError{http.StatusBadRequest, "directories nested too deep"} diff --git a/src/main.go b/src/main.go index 7e01e98..805c481 100644 --- a/src/main.go +++ b/src/main.go @@ -191,13 +191,13 @@ func usage() { fmt.Fprintf(os.Stderr, "(debug) "+ "git-pages {-get-blob|-get-manifest|-get-archive|-update-site} [file]\n") fmt.Fprintf(os.Stderr, "(admin) "+ - "git-pages {-freeze-domain |-unfreeze-domain }\n") + "git-pages {-freeze-domain|-unfreeze-domain} \n") fmt.Fprintf(os.Stderr, "(audit) "+ - "git-pages {-audit-log|-audit-server [args...]}\n") + "git-pages {-audit-log|-audit-read |-audit-rollback }\n") fmt.Fprintf(os.Stderr, "(audit) "+ - "git-pages {-audit-read|-audit-rollback|-audit-detach} \n") + "git-pages {-audit-expire |-audit-detach /}\n") fmt.Fprintf(os.Stderr, "(audit) "+ - "git-pages {-audit-expire }\n") + "git-pages -audit-server [args...]\n") fmt.Fprintf(os.Stderr, "(maint) "+ "git-pages {-run-migration |-trace-garbage|-size-histogram {original|stored}}\n") flag.PrintDefaults() @@ -239,12 +239,12 @@ func Main(versionInfo string) { "extract contents of audit record `id` to files '-*'") auditRollback := flag.String("audit-rollback", "", "restore site from contents of audit record `id`") - auditDetach := flag.String("audit-detach", "", - "detach all blobs of audit record `id`") - auditServer := flag.String("audit-server", "", - "listen for notifications on `endpoint` and spawn a process for each audit event") auditExpire := flag.String("audit-expire", "", "expire audit records older than `days` old") + auditDetach := flag.String("audit-detach", "", + "detach all blobs of audit records for a single `site` (or the entire domain with 'domain.tld/*')") + auditServer := flag.String("audit-server", "", + "listen for notifications on `endpoint` and spawn a process for each audit event") runMigration := flag.String("run-migration", "", "run a store `migration` (one of: create-domain-markers)") sizeHistogram := flag.String("size-histogram", "", @@ -273,9 +273,9 @@ func Main(versionInfo string) { *auditLog, *auditRead != "", *auditRollback != "", + *auditExpire != "", *auditDetach != "", *auditServer != "", - *auditExpire != "", *runMigration != "", *sizeHistogram != "", *traceGarbage, @@ -287,7 +287,7 @@ func Main(versionInfo string) { if cliOperations > 1 { logc.Fatalln(ctx, "-list-blobs, -list-manifests, -get-blob, -get-manifest, -get-archive, "+ "-update-site, -freeze-domain, -unfreeze-domain, -audit-log, -audit-read, "+ - "-audit-rollback, -audit-detach, -audit-server, -audit-expire, -run-migration, "+ + "-audit-rollback, -audit-expire, -audit-detach, -audit-server, -run-migration, "+ "-size-histogram, and -trace-garbage are mutually exclusive") } @@ -559,14 +559,36 @@ func Main(versionInfo string) { } case *auditDetach != "": - id, err := ParseAuditID(*auditDetach) - if err != nil { - logc.Fatalln(ctx, err) + domain, project, found := strings.Cut(*auditDetach, "/") + if !found || domain == "" || project == "" { + logc.Fatalln(ctx, "argument to -audit-detach must be in the form of "+ + "'domain.tld/project' or 'domain.tld/*'") } - err = backend.DetachAuditRecord(ctx, id) - if err != nil { - logc.Fatalln(ctx, err) + if project != "*" && project != ".index" { + if err := ValidateProjectName(project); err != nil { + logc.Fatalf(ctx, "audit detach: project name: %v\n", err) + } + } + + count := 0 + ids := backend.SearchAuditLog(ctx, SearchAuditLogOptions{}) + for record, err := range backend.GetAuditLogRecords(ctx, ids) { + if err != nil { + logc.Fatalln(ctx, err) + } + if record.GetDomain() == domain && (project == "*" || record.GetProject() == project) { + logc.Printf(ctx, "detaching audit record %s\n", record.GetAuditID()) + err = backend.DetachAuditRecord(ctx, record.GetAuditID()) + if err != nil { + logc.Fatalln(ctx, err) + } + count++ + } + } + + if count == 0 { + logc.Printf(ctx, "no audit records found for %s/%s", domain, project) } case *auditServer != "": diff --git a/src/pages.go b/src/pages.go index b5dc18d..addff83 100644 --- a/src/pages.go +++ b/src/pages.go @@ -143,7 +143,7 @@ func getPage(w http.ResponseWriter, r *http.Request) error { err = nil sitePath = strings.TrimPrefix(r.URL.Path, "/") if projectName, projectPath, hasProjectSlash := strings.Cut(sitePath, "/"); projectName != "" { - if IsValidProjectName(projectName) { + if ValidateProjectName(projectName) == nil { var projectManifest *Manifest var projectMetadata ManifestMetadata projectManifest, projectMetadata, err = backend.GetManifest(