Session 07 ยท ๐Ÿงฎ Policies

Fine-Grained Permissions

When "is this user an editor?" isn't enough, decide on attributes instead.

โœ“
7aABAC attributes
โœ“
7bpolicy as ordered rules
โœ“
7cthe three layers together

7a

Four groups of facts, one decision

"Editors may edit drafts in their own department during office hours" โ€” that rule needs all four.

SUBJECT
id 8 ยท role editor
dept sales ยท grants []
ACTION
edit
RESOURCE
owner 7 ยท dept sales
status draft
ENVIRONMENT
time 11:00
(also: IP, deviceโ€ฆ)
โ†’
POLICY ยท first match wins
โœ“ admin โ†’ anything
โœ— publish without grant
โœ— published โ†’ read-only
โœ“ same dept can read
โœ“ owner edits own draft
โœ“ editor ยท same dept ยท 9โ€“18h
โœ“ granted publisher ยท same dept
ยท default deny
โ†’
ALLOWeditor, same dept, office hours
at 22:00 โ†’ DENYdefault deny
RBAC
ABAC
decides on
role only
subject + action + resource + context
rule example
moderators delete posts
editors edit own-dept drafts, 9โ€“18h
complexity
tiny map
policy you must test
use when
coarse, app-wide access
rules depend on the data or the moment

7b

A policy is a list of rules, checked in order

Each rule matches or doesn't; the first match decides; nothing matching means no.

internal/authz/policy.go
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"
}
go run ./demos/07
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)
go run ./demos/07order ยท same rules, "read-only" moved below "owner edits"
asha  edit    published @11:00 โ†’ ALLOW  (owner edits own draft)
# one swapped line โ€” and a published doc is editable again
โ†’Deny rules go first. Order is part of the policy โ€” the second run proves it.
โ†’Explicit grants (doc:publish) give one user a power their role doesn't have โ€” store them in a user_permissions table.
โ†’Return the rule name with the decision and log it โ€” "why was I denied?" becomes answerable.
โœ“The table above is your test suite: one row per case, run on every change.

7c

Three layers, each asking a narrower question

Most APIs need RBAC and ownership; add ABAC only where a rule depends on the data.

1
Role / permissionMay this kind of user do this at all?
RequirePerm ยท 05
2
OwnershipIs this particular row theirs?
OwnerOrAdmin ยท 06
3
AttributesGiven its state and the moment โ€” still yes?
Decide(q) ยท 07
โ†’Layers 1โ€“2 fit in middleware. Layer 3 usually needs the loaded resource, so it runs in the service.
โ†’Keep every check in one authz package โ€” never scattered if user.Role == โ€ฆ in handlers.
TASK

Write โ€” and break โ€” a policy

  • Implement the 7 rules and the 8 cases above as a table-driven test.
  • Add: "nobody edits after a doc is archived". Where must it go in the list?
  • Swap two rules on purpose and watch which test fails.

Recap

Session 07 in four lines

1ABAC decides on subject, action, resource and environment together.
2A policy is ordered rules: deny rules first, first match wins, default deny.
3Explicit per-user grants cover the exceptions roles can't.
4Role โ†’ ownership โ†’ attributes: each layer narrows the previous one.