1package auditcatalog
2
3import (
4 "errors"
5 "fmt"
6 "slices"
7 "strings"
8)
9
10type Cause uint8
11
12const (
13 Request Cause = iota + 1
14 System
15 Either
16)
17
18type Actor string
19
20const (
21 ActorAdmin Actor = "admin"
22 ActorUser Actor = "user"
23 ActorSystem Actor = "system"
24)
25
26type Subject string
27
28const (
29 SubjectUser Subject = "user"
30 SubjectGroup Subject = "group"
31 SubjectGroupName Subject = "group-name"
32 SubjectInvitation Subject = "invitation"
33 SubjectSession Subject = "session"
34 SubjectLiteral Subject = "literal"
35)
36
37const SentencePrefix = "audit.action."
38
39type Entry struct {
40 Name string
41 Actors []Actor
42 Subjects []Subject
43 Detail []string
44 Cause Cause
45 Sentence string
46}
47
48var registry = map[string]Entry{}
49
50func Register(entries ...Entry) bool {
51 fresh := map[string]bool{}
52 for _, e := range entries {
53 if _, ok := registry[e.Name]; ok || fresh[e.Name] {
54 panic(fmt.Sprintf("audit action %q registered twice", e.Name))
55 }
56 if err := complete(e); err != nil {
57 panic(fmt.Sprintf("audit action %q: %v", e.Name, err))
58 }
59 fresh[e.Name] = true
60 }
61 for _, e := range entries {
62 registry[e.Name] = e
63 }
64 return true
65}
66
67func complete(e Entry) error {
68 switch {
69 case e.Name == "":
70 return errors.New("no name")
71 case len(e.Actors) == 0:
72 return errors.New("no actor kind")
73 case len(e.Subjects) == 0:
74 return errors.New("no subject kind")
75 case e.Cause < Request || e.Cause > Either:
76 return errors.New("no cause")
77 case e.Sentence != SentencePrefix+e.Name:
78 return fmt.Errorf("sentence key %q is not %q", e.Sentence, SentencePrefix+e.Name)
79 }
80 return nil
81}
82
83func Lookup(name string) (Entry, bool) {
84 e, ok := registry[name]
85 return e, ok
86}
87
88func Entries() []Entry {
89 out := make([]Entry, 0, len(registry))
90 for _, e := range registry {
91 out = append(out, e)
92 }
93 slices.SortFunc(out, func(a, b Entry) int { return strings.Compare(a.Name, b.Name) })
94 return out
95}