Documentation
¶
Overview ¶
Package authz is a lean relationship-based access control engine meant to be linked into a Go program. Its API mirrors SpiceDB's v1 PermissionsService: ReadRelationships, WriteRelationships and DeleteRelationships with preconditions, CheckPermission and CheckBulkPermissions, ExpandPermissionTree, LookupResources and LookupSubjects.
A Service is built from a datastore and a schema value (package schema). The schema is code: build it with schema.Build, or parse the SpiceDB DSL with package schema/dsl. ValidateStored checks at boot that the relationships already in the datastore still fit the schema in code.
AI: what is deliberately left out, and why. Caveats, zed tokens and the Watch service are absent: with one database behind the engine, every read runs at one snapshot and every write in one transaction, so there is no separate consistency vocabulary to expose. Schema storage is absent because an embedded engine's schema lives in the program that embeds it; a server built on this package persists schema text itself.
The package has no dependencies. It knows object types only as the strings the schema declares.
Index ¶
- Constants
- Variables
- func ValidName(s string) bool
- func ValidObjectID(s string) bool
- func ValidTypeName(s string) bool
- type BulkCreator
- type Change
- type CheckPermissionRequest
- type CheckPermissionResult
- type Datastore
- type Index
- type Indexed
- type LookupSubjectsResult
- type NestingIndex
- type ObjectRef
- type PermissionIndex
- type PermissionTree
- type Precondition
- type PreconditionOperation
- type Reader
- type Relationship
- type RelationshipFilter
- type RelationshipKind
- type RelationshipQuery
- type RelationshipUpdate
- type Revision
- type SubjectFilter
- type SubjectRef
- type TreeOperation
- type UpdateOperation
- type Writer
Constants ¶
const MaxObjectIDLength = 1024
MaxObjectIDLength is the longest object id accepted, as in SpiceDB.
const WildcardID = "*"
WildcardID is the subject id that stands for every object of its type.
Variables ¶
var ( ErrInvalidArgument = errors.New("authz: invalid argument") ErrRelationshipExists = errors.New("authz: relationship already exists") ErrPreconditionFailed = errors.New("authz: precondition failed") ErrMaxDepthExceeded = errors.New("authz: maximum resolution depth exceeded") ErrSchemaMismatch = errors.New("authz: stored relationships do not conform to the schema") )
Errors the API returns. Wrap-compatible: test with errors.Is.
Functions ¶
func ValidName ¶
ValidName reports whether s is a legal relation or permission name: lowercase, digits and underscores, starting with a letter, 1-64 chars.
func ValidObjectID ¶
ValidObjectID reports whether s is a legal object id (the SpiceDB charset).
func ValidTypeName ¶
ValidTypeName reports whether s is a legal object type name: a name as ValidName has it, optionally under namespaces separated by '/', as in SpiceDB (acme/user).
Types ¶
type BulkCreator ¶
type BulkCreator interface {
CreateAll(ctx context.Context, rels []Relationship) error
}
BulkCreator is a fast path a datastore may offer for loading many relationships at once (engine.Service.ImportRelationships). CreateAll creates every relationship in one transaction, records one Change, runs the datastore's hooks once, and fails with ErrRelationshipExists if any is already present.
type Change ¶
type Change struct {
Revision Revision
Updates []RelationshipUpdate
}
Change is what one committed transaction did to the relationships: every relationship that came into being (OperationTouch) or went (OperationDelete), in write order. Writes that changed nothing, such as a touch of a present relationship, do not appear.
AI: this is SpiceDB's Watch stream as a value type. Indexes (the materialize module) and applications that mirror grants into their own tables consume it; a datastore must record it in the same transaction as the write so a consumer never sees a change before its relationships.
type CheckPermissionRequest ¶
type CheckPermissionRequest struct {
Resource ObjectRef
Permission string
Subject SubjectRef
}
CheckPermissionRequest is one item of a CheckBulkPermissions call.
func (CheckPermissionRequest) Validate ¶
func (r CheckPermissionRequest) Validate() error
Validate checks the request is well formed; a wildcard cannot be checked.
type CheckPermissionResult ¶
type CheckPermissionResult struct {
Request CheckPermissionRequest
HasPermission bool
Err error
}
CheckPermissionResult answers one CheckPermissionRequest. Err is set when that item could not be evaluated; the other items are unaffected.
type Datastore ¶
type Datastore interface {
View(ctx context.Context, fn func(Reader) error) error
Transact(ctx context.Context, fn func(Writer) error) error
}
Datastore is what the engine runs on. View runs fn at one read snapshot; Transact runs fn in one transaction and commits only if fn returns nil.
A datastore stores tuples and their change log; it knows nothing about nesting. An index that accelerates nested usersets is a NestingIndex fed by the change log, either inside the write transaction through a hook the datastore package offers, or afterwards by following Changes.
type Index ¶
type Index interface {
NestingIndex
PermissionIndex
}
Index is what a datastore keeps beside its tuples to answer nesting and, when configured, whole permissions in one call: a NestingIndex that is also a PermissionIndex. A datastore that keeps one implements Indexed, and engine.New reads it; nothing else has to be wired.
type Indexed ¶
Indexed is a Datastore that keeps an Index of its own. Index returns nil when none is configured, and the engine then walks nesting and evaluates every permission from the tuples.
type LookupSubjectsResult ¶
LookupSubjectsResult is the set of subjects of one type that hold a permission. When Wildcard is true every subject of the type holds it except ExcludedSubjectIDs, and SubjectIDs is empty.
type NestingIndex ¶
type NestingIndex interface {
// NestedRelationships is Relationships over the usersets q names with its
// resource selector (ResourceType, ResourceIDs and Relation, all required)
// and every userset nested within them. Subject fields and Limit apply as
// in Relationships.
NestedRelationships(ctx context.Context, r Reader, q RelationshipQuery) ([]Relationship, error)
// NestedResourceIDs returns, sorted, the ids of every resourceType#relation
// userset that holds any of the named subjects directly or through
// nesting. A WildcardID among subjectIDs matches wildcard grants.
NestedResourceIDs(ctx context.Context, r Reader, subjectType string, subjectIDs []string, subjectRelation, resourceType, relation string) ([]string, error)
// NestedResourceIDsAmong is NestedResourceIDs restricted to the resource
// ids in among: which of them hold any of the named subjects. It is how a
// bulk check asks one question about many resources at once.
NestedResourceIDsAmong(ctx context.Context, r Reader, subjectType string, subjectIDs []string, subjectRelation, resourceType, relation string, among []string) ([]string, error)
}
NestingIndex answers questions about nested usersets in one call however deep the nesting, so the engine never walks nesting when the datastore keeps one (Indexed). Each method receives the Reader of the resolution in progress so an index kept in the same database can answer at the same snapshot.
Without an index the engine walks nesting hop by hop and each hop counts against the depth limit; with one, only arrows do.
type ObjectRef ¶
ObjectRef names one object: a type from the schema and an id.
type PermissionIndex ¶
type PermissionIndex interface {
// Materialized reports whether the index answers for resourceType#permission.
Materialized(resourceType, permission string) bool
// HasPermission reports whether subject holds the materialised permission
// on resource.
HasPermission(ctx context.Context, r Reader, resource ObjectRef, permission string, subject SubjectRef) (bool, error)
// ResourcesWithPermission returns, sorted, the ids of every resourceType on
// which subject holds the materialised permission.
ResourcesWithPermission(ctx context.Context, r Reader, resourceType, permission string, subject SubjectRef) ([]string, error)
// ResourcesWithPermissionAmong is ResourcesWithPermission restricted to
// the resource ids in among, for a bulk check: which of them the subject
// holds the permission on, sorted.
ResourcesWithPermissionAmong(ctx context.Context, r Reader, resourceType, permission string, subject SubjectRef, among []string) ([]string, error)
// SubjectsWithPermission returns, sorted, the ids of every subject of the
// given type and relation that holds the materialised permission on
// resource.
SubjectsWithPermission(ctx context.Context, r Reader, resource ObjectRef, permission, subjectType, subjectRelation string) ([]string, error)
}
PermissionIndex precomputes whole permissions: for each resource and each materialised permission, the usersets whose members hold it. It is Materialize's permission sets. The engine consults it for a permission the index reports as Materialized and evaluates the rewrite for every other, so an index may cover any subset of the schema. As with NestingIndex, each method receives the Reader of the resolution in progress.
AI: an index can only materialise a permission whose rewrite is unions, references and arrows over relations that allow neither wildcards nor usersets of a permission; intersections and exclusions have no set form. Those are Materialize's constraints too, and the index refuses anything else at construction rather than answering it wrongly.
type PermissionTree ¶
type PermissionTree struct {
Resource ObjectRef
Relation string
Operation TreeOperation
Subjects []SubjectRef // TreeLeaf only
Children []*PermissionTree // set operations only; Exclusion has exactly two
}
PermissionTree is what ExpandPermissionTree returns: the rewrite of one permission on one resource, one level of usersets deep, as SpiceDB expands it.
type Precondition ¶
type Precondition struct {
Operation PreconditionOperation
Filter RelationshipFilter
}
Precondition is checked inside the write transaction before any update is applied; a failure aborts the whole write with ErrPreconditionFailed.
func (Precondition) Validate ¶
func (p Precondition) Validate() error
Validate checks the precondition names an operation and a well-formed filter.
type PreconditionOperation ¶
type PreconditionOperation int
PreconditionOperation says what a Precondition requires of its filter.
const ( // PreconditionMustMatch requires at least one relationship to match. PreconditionMustMatch PreconditionOperation = iota + 1 // PreconditionMustNotMatch requires no relationship to match. PreconditionMustNotMatch )
type Reader ¶
type Reader interface {
Relationships(ctx context.Context, q RelationshipQuery) ([]Relationship, error)
RelationshipKinds(ctx context.Context) ([]RelationshipKind, error)
// Changes returns committed changes after the given revision in revision
// order, at most limit of them (0 for no limit). A caller has caught up
// when it gets back fewer than it asked for.
Changes(ctx context.Context, after Revision, limit int) ([]Change, error)
}
Reader is the read half of a datastore, always observed at one snapshot.
type Relationship ¶
type Relationship struct {
Resource ObjectRef
Relation string
Subject SubjectRef
}
Relationship is one tuple: Subject holds Relation on Resource.
func ParseRelationship ¶
func ParseRelationship(s string) (Relationship, error)
ParseRelationship reads the SpiceDB tuple form Relationship.String produces.
func (Relationship) IsNesting ¶
func (r Relationship) IsNesting() bool
IsNesting reports whether the relationship nests one userset within another: its subject is a userset. Indexes care about exactly these.
func (Relationship) String ¶
func (r Relationship) String() string
String renders the SpiceDB tuple form, type:id#relation@type:id[#relation].
func (Relationship) Validate ¶
func (r Relationship) Validate() error
Validate checks every part of the relationship is well formed. It does not consult a schema; the engine does that.
type RelationshipFilter ¶
type RelationshipFilter struct {
ResourceType string
ResourceID string
Relation string
Subject *SubjectFilter
}
RelationshipFilter selects relationships. ResourceType is required at the API; every other field narrows the selection when set.
func (RelationshipFilter) Query ¶
func (f RelationshipFilter) Query(limit int) RelationshipQuery
Query renders the filter as the datastore-level selection.
func (RelationshipFilter) Validate ¶
func (f RelationshipFilter) Validate() error
Validate checks the filter is well formed; ResourceType is required.
type RelationshipKind ¶
type RelationshipKind struct {
ResourceType string
Relation string
SubjectType string
SubjectRelation string
}
RelationshipKind is one distinct (resource type, relation, subject type, subject relation) combination present in the datastore.
type RelationshipQuery ¶
type RelationshipQuery struct {
ResourceType string
ResourceIDs []string
Relation string
SubjectType string
SubjectIDs []string
SubjectRelation *string
Limit int
}
RelationshipQuery is the datastore-level selection. Unlike RelationshipFilter it takes id sets, so a resolver can ask about many objects in one round trip, and no field is required.
type RelationshipUpdate ¶
type RelationshipUpdate struct {
Operation UpdateOperation
Relationship Relationship
}
RelationshipUpdate is one element of a WriteRelationships call, and one element of a Change, where Operation is OperationTouch for a relationship that came into being and OperationDelete for one that went.
type Revision ¶
type Revision uint64
Revision numbers committed write transactions in commit order. Zero is before the first.
type SubjectFilter ¶
SubjectFilter narrows a RelationshipFilter by subject. A nil Relation matches any subject relation; a pointer to "" matches only plain object subjects.
type SubjectRef ¶
SubjectRef is an object, or a userset (every subject holding Relation on the object) when Relation is set.
func (SubjectRef) String ¶
func (s SubjectRef) String() string
String renders the SpiceDB form, type:id or type:id#relation.
func (SubjectRef) Validate ¶
func (s SubjectRef) Validate() error
Validate checks the subject is well formed; a wildcard may carry no relation.
type TreeOperation ¶
type TreeOperation int
TreeOperation labels a PermissionTree node.
const ( // TreeLeaf holds the subjects a relation directly names. TreeLeaf TreeOperation = iota + 1 TreeUnion TreeIntersection TreeExclusion )
type UpdateOperation ¶
type UpdateOperation int
UpdateOperation is what a RelationshipUpdate does to its relationship.
const ( // OperationCreate writes the relationship and fails with // ErrRelationshipExists when it is already present. OperationCreate UpdateOperation = iota + 1 // OperationTouch writes the relationship if absent and succeeds either way. OperationTouch // OperationDelete removes the relationship and succeeds either way. OperationDelete )
type Writer ¶
type Writer interface {
Reader
// Create fails with ErrRelationshipExists when the relationship is present.
Create(ctx context.Context, r Relationship) error
Touch(ctx context.Context, r Relationship) error
Delete(ctx context.Context, r Relationship) error
DeleteMatching(ctx context.Context, q RelationshipQuery) (int64, error)
}
Writer is a Reader inside one transaction.
Directories
¶
| Path | Synopsis |
|---|---|
|
datastore
|
|
|
memory
Package memory is an in-process authz.Datastore for tests.
|
Package memory is an in-process authz.Datastore for tests. |
|
postgres
Package postgres is the authz.Datastore on Postgres, via pgx.
|
Package postgres is the authz.Datastore on Postgres, via pgx. |
|
internal
|
|
|
datastoretest
Package datastoretest is the conformance suite every authz.Datastore must pass.
|
Package datastoretest is the conformance suite every authz.Datastore must pass. |
|
materialize
Package materialize is the generic core of the indexes that precompute what the engine would otherwise walk: the closure of userset nesting, after Zanzibar's Leopard, and permission sets, after AuthZed's Materialize.
|
Package materialize is the generic core of the indexes that precompute what the engine would otherwise walk: the closure of userset nesting, after Zanzibar's Leopard, and permission sets, after AuthZed's Materialize. |
|
pgtest
Package pgtest serialises test packages that share one scratch database.
|
Package pgtest serialises test packages that share one scratch database. |
|
postgres
module
|
|
|
Package schema is the model the engine runs on: object types, the relations they store, and the permissions computed from them.
|
Package schema is the model the engine runs on: object types, the relations they store, and the permissions computed from them. |
|
dsl
Package dsl parses the SpiceDB schema DSL into a schema.Schema.
|
Package dsl parses the SpiceDB schema DSL into a schema.Schema. |