When "is this user an editor?" isn't enough, decide on attributes instead.
7a
"Editors may edit drafts in their own department during office hours" โ that rule needs all four.
7b
Each rule matches or doesn't; the first match decides; nothing matching means no.
type Req struct { Sub Subject // id, role, dept, grants Act string // "read", "edit", "publish" Res Resource // owner, dept, status Now time.Time // environment } type Rule struct { Name string Match func(q Req) bool Allow bool } var policy = []Rule{ {"publish needs explicit grant", func(q Req) bool { return q.Act == "publish" && !slices.Contains(q.Sub.Grants, "doc:publish") }, false}, {"owner edits own draft", func(q Req) bool { return q.Act == "edit" && q.Sub.ID == q.Res.OwnerID }, true}, // โฆ 5 more rules } func Decide(q Req) (bool, string) { for _, rule := range policy { if rule.Match(q) { return rule.Allow, rule.Name } } return false, "default deny" }
asha edit draft @11:00 โ ALLOW (owner edits own draft)
ravi edit draft @11:00 โ ALLOW (editor, same dept, office hours)
ravi edit draft @22:00 โ DENY (default deny)
omar edit draft @11:00 โ DENY (default deny)
ravi publish draft @11:00 โ DENY (publish needs explicit grant)
lead publish draft @11:00 โ ALLOW (granted publisher, same dept)
asha edit published @11:00 โ DENY (published docs are read-only)
omar read published @11:00 โ DENY (default deny)
asha edit published @11:00 โ ALLOW (owner edits own draft) # one swapped line โ and a published doc is editable again
doc:publish) give one user a power their role doesn't have โ store them in a user_permissions table.7c
Most APIs need RBAC and ownership; add ABAC only where a rule depends on the data.
authz package โ never scattered if user.Role == โฆ in handlers.Recap