Documentation
¶
Overview ¶
Package fga holds OpenFGA domain helpers shared by the CLI commands and the TUI: parsing tuple shorthand and turning an authorization model into a graph.
Index ¶
- Variables
- func ConditionRows(c *openfga.RelationshipCondition) [][2]string
- func DecodeStrictJSON(data []byte, v any) error
- func ExpandTree(root *ResNode, rootRef string, expand Expander, ...) bool
- func FormatContextJSON(ctx map[string]any) string
- func FormatContextualTuple(k openfga.TupleKey) string
- func FormatTuple(k openfga.TupleKey) string
- func FormatUserset(object, relation, user string) string
- func MarkGranted(root *ResNode, user string, r GrantResolver) bool
- func ParseJSONObject(label, s string) (map[string]any, error)
- func ParseTuple(user, relation, object string) (openfga.TupleKey, error)
- func RenderResolution(root *ResNode, user, object, relation string) string
- func SplitObject(object string) (typ, id string)
- func SplitTTU(label string) (target, via string, ok bool)
- func Triple(args []string, userFlag, relationFlag, objectFlag string) (user, relation, object string, err error)
- func ValidateObjectRef(object string) error
- func ValidateReadObject(s string) error
- func ValidateReadRelation(s string) error
- func ValidateReadUser(s string) error
- func ValidateUserRef(user string) error
- type DiagramEdge
- type Expander
- type GrantResolver
- type Graph
- type ReadFilter
- type Relation
- type RelationEdge
- type ResNode
- type ResOp
- type TypeNode
Constants ¶
This section is empty.
Variables ¶
var ( ErrReadFilterNeedsObject = errors.New("the filter needs an object — a whole type (document:) or one object (document:roadmap)") ErrReadFilterBareType = errors.New("a bare object type isn't enough — add an object id (document:roadmap) or a user") )
The two halves of the cross-field rule, exported so each surface can phrase them for its own audience — a form names its fields, a command names flags.
Functions ¶
func ConditionRows ¶
func ConditionRows(c *openfga.RelationshipCondition) [][2]string
ConditionRows renders a tuple's condition as label/value pairs for detail views: a "Condition" row with the name, and a "Condition Context" row with the context as compact JSON when present. Returns nil for an unconditioned tuple.
func DecodeStrictJSON ¶ added in v0.267.0
DecodeStrictJSON JSON-decodes data into v, rejecting unknown fields anywhere in the value so a mistyped field name surfaces as a parse error instead of being silently dropped. It also rejects trailing data after the value, matching json.Unmarshal's stricter behavior (json.Decoder.Decode alone only reads one value and ignores what follows).
Every document the CLI accepts from a user should go through this: dropping an unrecognised field quietly turns a typo into a semantic change, since the field the author meant to set keeps its zero value.
func ExpandTree ¶
func ExpandTree(root *ResNode, rootRef string, expand Expander, tupleset func(object, relation string) []string, maxDepth, maxNodes int) bool
ExpandTree recursively expands computed-userset and tuple-to-userset leaves in place, attaching each reference's own resolution subtree so nested branches appear instead of dead-end leaves — e.g. a `viewer` node that resolves through `owner` gains `owner`'s subtree (and, in turn, its users) as a child.
OpenFGA's Expand API resolves only one level, so this issues a fresh expand per referenced relation via the `expand` callback; `tupleset` lists the objects a tuple-to-userset points at. rootRef ("object#relation" of root) seeds cycle detection. maxDepth bounds recursion depth and maxNodes bounds the total number of expansions (i.e. extra API calls), so a deep or cyclic model can't fan out unbounded. Call this before MarkGranted. ExpandTree returns whether it was truncated: an expandable arm (a computed or tuple-to-userset leaf) was left unexpanded because the depth or node budget ran out. A cycle stop does NOT count as truncation (it is correct). A caller computing coverage from the tree should treat a truncated tree as partial — arms below the cut were never evaluated and so can't be credited.
func FormatContextJSON ¶
FormatContextJSON renders a context map as compact JSON, or "" when empty.
func FormatContextualTuple ¶
FormatContextualTuple renders a contextual tuple in userset shorthand, with a " [conditionName]" suffix when the tuple carries a condition.
func FormatTuple ¶
func FormatUserset ¶
FormatUserset renders a tuple's parts in userset shorthand: "object#relation@user". Taking parts rather than a typed key lets it serve both openfga.TupleKey and openfga.CheckRequestTupleKey (which lack a common interface).
func MarkGranted ¶
func MarkGranted(root *ResNode, user string, r GrantResolver) bool
MarkGranted annotates each node with whether it grants `user`. Direct-user leaves match the user string exactly, computed usersets resolve via a Check, and tuple-to-userset leaves read the tupleset then Check the computed relation on each related object. It returns the root's grant status.
func ParseJSONObject ¶
ParseJSONObject parses s as a JSON object into a map, returning nil for empty input. label names the field in the error message (e.g. "--context" or "context") so both the CLI and TUI can share one implementation.
func ParseTuple ¶
ParseTuple parses the canonical "user relation object" triple from three separate arguments and returns a TupleKey. Each part is validated lightly: user and object should look like "type:id" (user may also carry "#relation" or be a wildcard "type:*").
func RenderResolution ¶
RenderResolution draws the resolution rooted at object#relation as the playground-style node-link diagram. user is the queried user (its box is highlighted); nodes and connectors on the granting branch are tinted.
func SplitObject ¶
SplitObject splits "type:id" into its type and id components.
func SplitTTU ¶ added in v0.266.0
SplitTTU parses a tuple-to-userset edge label of the form "target from via".
func Triple ¶
func Triple(args []string, userFlag, relationFlag, objectFlag string) (user, relation, object string, err error)
Triple resolves a user/relation/object triple from positional args and the --user/--relation/--object flags. Flags set the fields they name; the remaining positionals then fill the still-unset fields left to right. This means `--user user:anne viewer document:roadmap` reads the two positionals as relation and object (rather than shifting them by index). Extra positionals that can't fill an unset field are an error, so a flag and a positional never silently fight over the same field. It errors if any part is missing.
func ValidateObjectRef ¶
ValidateObjectRef checks that object is a concrete "type:id" (no wildcard, no userset), matching ParseTuple's object rules.
func ValidateReadObject ¶ added in v0.267.0
ValidateReadObject checks a /read filter's object on its own: a whole type ("document:") or one object ("document:roadmap"). The server's combination rule spans fields, so it lives in ReadFilter.Validate; this is what a form can check as the user types, and what a flag can check in isolation.
func ValidateReadRelation ¶ added in v0.267.0
ValidateReadRelation checks a /read filter's relation on its own. A relation is a bare name — the server's own pattern is ^[^:#@\s]{1,50}$ — so anything shaped like a reference is a swapped field rather than a relation.
func ValidateReadUser ¶ added in v0.267.0
ValidateReadUser checks a /read filter's user on its own. Unlike a tuple being written, it is optional, and /read matches usersets and wildcards, so this only asks for a type — the exact formats are the server's business.
func ValidateUserRef ¶
ValidateUserRef checks that user is a "type:id", a wildcard "type:*", or a userset "type:id#relation" — the same shape ParseTuple accepts for the user position — so a swapped argument yields a friendly hint, not a raw 400.
Types ¶
type DiagramEdge ¶
type DiagramEdge struct {
From string `json:"from"`
To string `json:"to"`
Kind string `json:"kind"`
Via string `json:"via"`
}
DiagramEdge is a directed dependency between two object types: type From has a relation that can be satisfied by users (or usersets) of type To. Kind is "direct" or "ttu" (tuple-to-userset / inherited). Via names the relation the dependency flows through (the relation on From for direct edges, the tupleset relation for ttu edges).
type Expander ¶
Expander resolves an object#relation to its (single-level) Expand subtree, or nil when it can't be expanded (API error, or no such resolution).
type GrantResolver ¶
type GrantResolver struct {
Check func(user, relation, object string) bool
Tupleset func(object, relation string) []string
// CheckDirectLeaves, when true, makes a direct-user leaf defer to Check so
// tuple conditions are honored; this requires Check to forward the request
// context (Context/ContextualTuples) so an ABAC-conditioned direct tuple is
// evaluated against the same context the engine used. When false (the
// default) direct membership from the Expand tree is trusted as-is — the
// cheap, context-free behavior a caller with no context (e.g. the
// playground) wants, avoiding a remote Check per direct leaf.
CheckDirectLeaves bool
}
GrantResolver supplies the live lookups MarkGranted needs. Check reports whether `user` has `relation` on `object`. Tupleset returns the objects related to `object` via `relation` — the "user" side of matching tuples — and may be nil to skip tuple-to-userset resolution.
type Graph ¶
type Graph struct {
SchemaVersion string `json:"schema_version"`
Types []TypeNode `json:"types"`
// Edges are the inter-type dependencies used to draw the node-link diagram.
Edges []DiagramEdge `json:"edges"`
// contains filtered or unexported fields
}
Graph is the parsed, render-ready view of an authorization model.
func ParseModel ¶
func ParseModel(m *openfga.AuthorizationModel) Graph
ParseModel converts an authorization model into a Graph by interpreting the relation rewrite rules and the directly-related-user-types metadata. Slices are initialized (never nil) so `--json` output serializes empty collections as [] rather than null.
func (Graph) RelationsForObject ¶ added in v0.267.0
RelationsForObject returns every relation defined on the type of object (the part before the ":"), in the graph's sorted order. It backs the list-relations query, which tests a user against all of them. It errors when object carries no type, the type is absent from the model, or the type has no relations — none of which yield anything to test.
func (Graph) RenderDiagram ¶
RenderDiagram draws the authorization model as a node-link diagram: one rounded card per object type (a colored header plus its relations) laid out left→right by dependency depth, with edges routed orthogonally around the cards (never through them) and colored by resolution kind. The result is a wide, multi-line string meant to be shown inside a scrollable/pannable viewport.
func (Graph) RenderWeightedDiagram ¶ added in v0.265.0
RenderWeightedDiagram draws the fully-expanded weighted graph (relation, operator, direct-grouping and terminal-type nodes with per-terminal-type weights), in the style of openfga/model-visualizer. It is built lazily so the text and JSON model-graph outputs, which never use it, do not pay for it.
type ReadFilter ¶ added in v0.267.0
ReadFilter is the tuple_key a /read request narrows on. The zero value means no filter: read the whole store.
It lives here so the CLI's `tuples read` and the playground's Tuples pane share one shape and one rule — the server's cross-field requirement is easy to trip and its 400 says nothing useful, so both surfaces catch it locally.
func NewReadFilter ¶ added in v0.267.0
func NewReadFilter(user, relation, object string) ReadFilter
NewReadFilter builds a filter from raw input, trimming each field. Both surfaces need this: the server's own patterns reject whitespace, so a padded value would pass the local check and then fail the round trip it exists to prevent — and a whitespace-only field must read as unset, not as a filter.
func (ReadFilter) Active ¶ added in v0.267.0
func (f ReadFilter) Active() bool
Active reports whether the filter narrows anything.
func (ReadFilter) TupleKey ¶ added in v0.267.0
func (f ReadFilter) TupleKey() *openfga.ReadRequestTupleKey
TupleKey renders the filter for a ReadRequest, or nil when nothing is set — /read treats an all-empty tuple_key differently from an absent one.
func (ReadFilter) Validate ¶ added in v0.267.0
func (f ReadFilter) Validate() error
Validate mirrors the server-side /read tuple_key rule (openfga v1.18.1, ReadQuery.Execute): the object must carry a type, and the object id and user cannot both be empty. The zero filter is valid — it means "no filter".
The object is split on its first colon exactly as the server splits it (tuple.SplitObject), so a colon-less object yields no type and is caught here rather than round-tripping into a 400. This is the cross-field half of the rule, which no single field validator can express; the per-field halves are ValidateReadUser, ValidateReadRelation and ValidateReadObject.
type Relation ¶
type Relation struct {
Name string `json:"name"`
Edges []RelationEdge `json:"edges"`
// Weight is the worst-case resolution cost (>=1); -1 when Recursive.
Weight int `json:"weight"`
Recursive bool `json:"recursive"`
}
Relation is a single relation on a type with its resolution edges.
type RelationEdge ¶
type RelationEdge struct {
// Kind is "direct", "computed", or "ttu" (tuple-to-userset).
Kind string `json:"kind"`
// Label is a human-readable description of the edge target.
Label string `json:"label"`
}
RelationEdge describes one resolution path for a relation.
type ResNode ¶
type ResNode struct {
Name string // the "object#relation" this node resolves
Op ResOp // how Children combine (ResLeaf → no children)
Children []*ResNode // operands for union / intersection / exclusion
// Leaf payloads — at most one is populated when Op == ResLeaf:
Users []string // direct users/usersets, e.g. ["user:anne", "team:eng#member"]
Computed string // a computed userset, e.g. "document:roadmap#owner"
TTUFrom string // tuple-to-userset: the tupleset relation, e.g. "document:x#parent"
TTUTo []string // the computed usersets reached through that tupleset
Granted bool // set by MarkGranted: this node reaches the queried user
}
ResNode is one node of a Check resolution. Each node resolves a single object#relation, either as a leaf (direct users, a computed userset, or a tuple-to-userset) or as a boolean combination of child nodes.
func GrantedPath ¶
GrantedPath returns a pruned copy of the tree keeping only the branch(es) that reach the user — the ACL resolution path. It returns nil when nothing grants (e.g. a denied check). Call MarkGranted first.