authzstore

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 16, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

README

authzstore

Hanzo's xorm-backed adapter for hanzoai/authz policies. Implements both persist.Adapter and persist.FilteredAdapter on top of a caller-provided *xorm.Engine.

Replaces hanzoai/xorm-adapter/v3 (which was a brand-renamed fork of casdoor/xorm-adapter) with a focused 270-line package that:

  • operates directly against the canonical (ptype, v0, v1, v2, v3, v4, v5) row shape — wire-compatible with any existing authz_*_rule / casbin_*_rule tables;
  • runs SavePolicy inside a single engine.Transaction (DELETE + batched INSERT) — no deny-all window on crash;
  • picks the INSERT batch size at runtime via pragma_compile_options for SQLite (respecting SQLITE_MAX_VARIABLE_NUMBER) and uses 8000-row batches on Postgres / MySQL;
  • validates the table name against ^[a-zA-Z_][a-zA-Z0-9_]*$ at construction so callers cannot smuggle DDL through the string-concatenated WHERE / DELETE fragments.

Install

go get github.com/hanzoai/authzstore

Use

import (
    authzengine "github.com/hanzoai/authz"
    "github.com/hanzoai/authzstore"
    "github.com/hanzoai/xorm"
)

eng, err := xorm.NewEngine("sqlite", "iam.db")
if err != nil { return err }

adapter, err := authzstore.New(eng, "authz_api_rule", "")
if err != nil { return err }

e, err := authzengine.NewEnforcer("model.conf", adapter)

The struct tags on AuthzRule match the historical xorm-adapter DDL byte- for-byte, so Sync2 against a pre-existing populated table is a no-op.

Tests

go test -count=1 -race .

Includes a concurrent reader/writer test that proves SavePolicy never exposes a deny-all window mid-rewrite.

License

Apache-2.0.

Documentation

Overview

Package authzstore is the IAM-owned hanzoai/authz policy adapter.

It replaces github.com/hanzoai/xorm-adapter/v3 with a small, focused store that operates directly against the existing on-disk schema (table columns: ptype, v0, v1, v2, v3, v4, v5) via the hanzoai/xorm engine already in use across the IAM. The table layout is unchanged — both prod clusters carry data in `authz_user_rule` and `authz_api_rule` and that physical schema is the contract.

Design notes

  1. ONE adapter type for both standard and filtered loads — implements both persist.Adapter and persist.FilteredAdapter. The authz library type-asserts to FilteredAdapter when LoadFilteredPolicy is invoked.
  2. The xorm engine is supplied by the caller (typically the global ormer.Engine). This package does not open or close engines; the IAM owns engine lifecycle.
  3. The AuthzRule struct lives here and is the single canonical row type. util.AuthzRule is a type alias to this struct so callers (controllers, util helpers) keep their existing imports working.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Adapter

type Adapter struct {
	// contains filtered or unexported fields
}

Adapter is the hanzoai/authz adapter backed by a hanzoai/xorm engine pointed at the table named by `table` (already-prefixed if the deployment uses a tableNamePrefix).

func New

func New(engine *xorm.Engine, tableName, tablePrefix string) (*Adapter, error)

New constructs an Adapter for the given engine and table.

`tableName` is the unprefixed table (e.g. "authz_user_rule"). If `tablePrefix` is non-empty it is concatenated as-is — callers should include any trailing underscore in the prefix value, matching how the rest of the IAM uses tableNamePrefix.

The table is created if it does not exist. This matches the xorm-adapter behavior the IAM has historically relied on for first boot; production deployments already have the rows so the CREATE is a no-op there.

func (*Adapter) AddPolicies added in v0.1.1

func (a *Adapter) AddPolicies(_ string, ptype string, rules [][]string) error

AddPolicies inserts a batch of rules in a single transaction. Satisfies persist.BatchAdapter so casbin's initAdminPermission and bulk-policy loads don't panic with "missing method AddPolicies".

func (*Adapter) AddPolicy

func (a *Adapter) AddPolicy(_ string, ptype string, rule []string) error

AddPolicy inserts a single rule.

func (*Adapter) IsFiltered

func (a *Adapter) IsFiltered() bool

IsFiltered reports whether the last load was a filtered load.

func (*Adapter) LoadFilteredPolicy

func (a *Adapter) LoadFilteredPolicy(model authzmodel.Model, filter interface{}) error

LoadFilteredPolicy loads only rows matching the given Filter.

func (*Adapter) LoadPolicy

func (a *Adapter) LoadPolicy(model authzmodel.Model) error

LoadPolicy reads every row and loads it into the model.

func (*Adapter) RemoveFilteredPolicy

func (a *Adapter) RemoveFilteredPolicy(_ string, ptype string, fieldIndex int, fieldValues ...string) error

RemoveFilteredPolicy removes rules whose V[i..i+len(fieldValues)-1] columns match the supplied values. The semantics mirror the xorm-adapter so consumers see identical behavior.

func (*Adapter) RemovePolicies added in v0.1.1

func (a *Adapter) RemovePolicies(_ string, ptype string, rules [][]string) error

RemovePolicies removes a batch of rules in a single transaction. Each rule is matched on the full (ptype, v0..v5) tuple — same semantics as RemovePolicy.

func (*Adapter) RemovePolicy

func (a *Adapter) RemovePolicy(_ string, ptype string, rule []string) error

RemovePolicy removes a single rule matched exactly on (ptype, v0..v5). The MustCols call forces xorm to include zero-valued columns in the WHERE clause — without it, deleting a rule like {"p", "u", "r1", "", "", "", ""} would match any row sharing u + r1 regardless of V2..V5.

func (*Adapter) SavePolicy

func (a *Adapter) SavePolicy(model authzmodel.Model) error

SavePolicy atomically rewrites the rule set: DELETE every row, then INSERT every rule the model holds, all inside a single transaction.

The legacy xorm-adapter implementation did DROP TABLE → CREATE → INSERT outside a tx, so a crash between the DROP and the bulk INSERT left the next pod loading an empty policy — every Enforce() then returned false (deny-all). We preserve the table+indexes (already Sync2'd at boot) and rewrite the rows under a single transaction boundary: either every row in the new model is visible, or every pre-existing row is visible. Never a half-empty table.

type AuthzRule

type AuthzRule struct {
	Id    int64  `xorm:"pk autoincr" json:"id,omitempty"`
	Ptype string `xorm:"varchar(100) index not null default ''" json:"ptype"`
	V0    string `xorm:"varchar(100) index not null default ''" json:"v0"`
	V1    string `xorm:"varchar(100) index not null default ''" json:"v1"`
	V2    string `xorm:"varchar(100) index not null default ''" json:"v2"`
	V3    string `xorm:"varchar(100) index not null default ''" json:"v3"`
	V4    string `xorm:"varchar(100) index not null default ''" json:"v4"`
	V5    string `xorm:"varchar(100) index not null default ''" json:"v5"`
}

AuthzRule is one policy row in the `authz_*_rule` family of tables. Field tags match the historical schema produced by the xorm-adapter fork — Id is an autoincrement primary key and the six V columns are indexed varchar(100). Changing this struct changes the migration; do not edit lightly.

type Filter

type Filter struct {
	Ptype []string
	V0    []string
	V1    []string
	V2    []string
	V3    []string
	V4    []string
	V5    []string
}

Filter narrows a LoadFilteredPolicy call to rows whose columns are in the supplied sets. An empty slice for a column means "no constraint on this column".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL