casbinbunadapter

package module
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Nov 5, 2026 License: MIT Imports: 22 Imported by: 0

README

Casbin Bun Adapter for Postgres

GoDoc Go Report Card GitHub tag

Table of Contents

About

It is just adapter for Casbin using Golang ORM called Bun.

This adapter supports listening to the policies update in database via triggers, so when something is changed in database then your application would be aware of it.

AutoSave feature is implemented.

Attentions/warnings:

  • PostgreSQL is only supported database currently (since of heavy trigger feature usage)! As for other databases (MySQL, SQLite, Microsoft SQL Server) PRs are welcome.

  • This repository is not pretend to be the best Casbin adapter, but it works for my use-cases. Check out others implementations here

Installation

go get github.com/LdDl/casbin-bun-adapter

Usage

Examples:

  1. Plain example without AutoSave or PostgreSQL triggers involed - ./examples/custom_names
  2. Example with using AutoSave feature - ./examples/autosave_changes. Just add this line after *casbin.Enforcer is initialized:
    // ...
    enforcer.EnableAutoSave(true)
    // ...
    
  3. Transactional journal with strict checks and background synchronization - ./examples/journal.

Storage

Rules support six fields (v0 through v5, type *string). NULL marks unused columns; empty strings retain their positions. Existing empty padding requires normalization to NULL according to the model.

NewCasbinPolicyFrom returns (CasbinPolicy, error). Empty filter values are wildcards; an all-wildcard filter removes every rule of the specified type.

Transactional journal

Policies can be written through SQL or a Casbin enforcer using BunAdapter. Authorization uses a separate JournalReader. SavePolicy replaces the entire policy table.

Requires PostgreSQL 14+, a primary connection, unique policy rows and a permanent table without inheritance or partitions. Policy writers are serialized until commit; PREPARE TRANSACTION is unsupported.

The table owner creates the journal in a new schema and grants journal access to an existing application role:

err := adapter.CreateJournal(ctx, "casbin_journal", "app_user")
if err != nil {
    return err
}

The application role also needs SELECT on the policy table. Using its connection:

reader, err := adapter.NewJournalReader(ctx, "casbin_journal", policyModel,
    casbinbunadapter.JournalReaderOptions{Listen: true})
if err != nil {
    return err
}

allowed, err := reader.EnforceContext(ctx, "alice", "/reports", "read")
if err != nil {
    return err
}
if !allowed {
    return errors.New("access denied")
}

Each check reads the journal position and synchronizes before deciding. Synchronization errors deny access with ErrJournalNotReady; the default timeout is five seconds. Small changes apply incrementally; large changes or missing history reload a consistent snapshot.

A background worker receives notifications and removes journal history older than seven days:

err := reader.Run(ctx)
if err != nil && !errors.Is(err, context.Canceled) {
    return err
}

Run blocks until cancellation. The runnable example includes startup and shutdown.

Option or method Behavior
Listen: true Dedicated pgdriver connection; direct PostgreSQL or session pooling.
PollInterval Optional polling, disabled by default.
EnforceCached No database request; may miss changes for up to MaxStaleness (default 30 seconds).
Status() Synchronization, listener and cleanup errors.
Sync(ctx) Manual synchronization; strict checks also work without Run.

Transaction pooling cannot be used for LISTEN. Standard models keep a canonical policy set and two enforcers in memory. Custom setup belongs in Configure; mutable state must remain private to each enforcer.

Tests

go test ./...
CASBIN_TEST_DSN='postgres://localhost/casbin_test?sslmode=disable' go test ./...
CASBIN_TEST_DSN='postgres://localhost/casbin_test?sslmode=disable' go test -run '^$' -bench PostgresJournal -benchtime=3x -benchmem

Database tests require PostgreSQL 15+ and schema creation permission in a dedicated test database. Journal privilege tests also require role creation and switching permissions. Without CASBIN_TEST_DSN, database tests are skipped.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrJournalNotReady = errors.New("policy journal is not synchronized")

ErrJournalNotReady indicates that authorization data cannot currently be trusted.

Functions

func WithMatcherOptions

func WithMatcherOptions(matcher MatcherOptions) func(*BunAdapter)

WithMatcherOptions overrides default matching options. If some of keys are empty strings than default values will be applied

Types

type BunAdapter

type BunAdapter struct {
	*bun.DB
	// contains filtered or unexported fields
}

BunAdapter is just wrapper around *bun.DB

func NewBunAdapter

func NewBunAdapter(bunConnection *bun.DB, opts ...func(*BunAdapter)) *BunAdapter

NewBunAdapter returns new *BunAdapter. Connections to database must be provided. Other arguments are optional

func (*BunAdapter) AddPolicy

func (adapter *BunAdapter) AddPolicy(sec string, ptype string, rule []string) error

AddPolicy adds a policy rule to the storage. Needed for AutoSave, see the ref. https://casbin.org/docs/adapters/#autosave

func (*BunAdapter) CreateJournal added in v0.0.2

func (adapter *BunAdapter) CreateJournal(ctx context.Context, schemaName string, readerRoles ...string) error

CreateJournal installs a transactional change journal in a new, dedicated schema. It requires the policy table owner or a migration role with equivalent privileges. Optional reader roles receive schema usage and SELECT on the journal tables. Journal creation does not start synchronization with an enforcer.

func (*BunAdapter) EnableJournalCleanup added in v0.0.2

func (adapter *BunAdapter) EnableJournalCleanup(ctx context.Context, schemaName string, readerRoles ...string) error

EnableJournalCleanup installs bounded cleanup for an existing journal as its owner. Reader roles can invoke cleanup but cannot choose its retention period or delete arbitrary rows.

func (*BunAdapter) EnableJournalNotifications added in v0.0.2

func (adapter *BunAdapter) EnableJournalNotifications(ctx context.Context, schemaName string) error

EnableJournalNotifications installs commit notifications for an existing journal as its owner.

func (*BunAdapter) LoadPolicy

func (adapter *BunAdapter) LoadPolicy(model model.Model) error

LoadPolicy loads all policy rules from the storage

func (*BunAdapter) NewJournalReader added in v0.0.2

func (adapter *BunAdapter) NewJournalReader(ctx context.Context, schemaName string, m model.Model, options JournalReaderOptions) (*JournalReader, error)

NewJournalReader loads policies and the journal position from one database snapshot.

func (*BunAdapter) PruneJournal added in v0.0.2

func (adapter *BunAdapter) PruneJournal(ctx context.Context, schemaName string) (int, error)

PruneJournal removes up to 1000 complete transactions older than seven days.

func (*BunAdapter) RemoveFilteredPolicy

func (adapter *BunAdapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error

RemoveFilteredPolicy removes policy rules that match the filter from the storage. Needed for AutoSave, see the ref. https://casbin.org/docs/adapters/#autosave

func (*BunAdapter) RemovePolicy

func (adapter *BunAdapter) RemovePolicy(sec string, ptype string, rule []string) error

RemovePolicy removes a policy rule from the storage. Needed for AutoSave, see the ref. https://casbin.org/docs/adapters/#autosave

func (*BunAdapter) SavePolicy

func (adapter *BunAdapter) SavePolicy(model model.Model) error

SavePolicy saves all policy rules to the storage

type CasbinPolicy

type CasbinPolicy struct {
	bun.BaseModel `bun:"casbin_policies,alias:t"`
	ID            int     `bun:"id,pk,autoincrement" json:"id"`
	PType         string  `bun:"ptype,type:varchar(2),notnull,default:'p'" json:"ptype"`
	V0            *string `bun:"v0,type:varchar(256)" json:"v0"`
	V1            *string `bun:"v1,type:varchar(256)" json:"v1"`
	V2            *string `bun:"v2,type:varchar(256)" json:"v2"`
	V3            *string `bun:"v3,type:varchar(256)" json:"v3"`
	V4            *string `bun:"v4,type:varchar(256)" json:"v4"`
	V5            *string `bun:"v5,type:varchar(256)" json:"v5"`
}

CasbinPolicy is database storage format following the below https://casbin.org/docs/policy-storage#database-storage-format Template on which Golang struct has been prepared: CREATE TABLE casbin_policies (

  id int4 GENERATED BY DEFAULT AS IDENTITY( INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START 1 CACHE 1 NO CYCLE) NOT NULL,
	ptype varchar(2) DEFAULT 'p'::character varying NOT NULL,
	v0 varchar(256) null,
	v1 varchar(256) null,
	v2 varchar(256) null,
	v3 varchar(256) null,
	v4 varchar(256) null,
	v5 varchar(256) null,
	CONSTRAINT casbin_policies_pk PRIMARY KEY (id),
	CONSTRAINT casbin_policies_unique UNIQUE NULLS NOT DISTINCT (ptype, v0, v1, v2, v3, v4, v5) -- Aware! This will work only for Postgres 15+. For the ref. see: https://stackoverflow.com/a/8289253/6026885

);

func NewCasbinPolicyFrom

func NewCasbinPolicyFrom(ptype string, rule []string) (CasbinPolicy, error)

NewCasbinPolicyFrom preserves empty values and leaves unused columns NULL.

type JournalReader added in v0.0.2

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

JournalReader owns a read-only authorization view; policies are changed in PostgreSQL.

func (*JournalReader) Enforce added in v0.0.2

func (reader *JournalReader) Enforce(values ...interface{}) (bool, error)

Enforce checks against policies at least as recent as the journal position read for this call. The adapter must connect to the primary PostgreSQL database.

func (*JournalReader) EnforceCached added in v0.0.2

func (reader *JournalReader) EnforceCached(values ...interface{}) (bool, error)

EnforceCached checks only the local view and may miss recently committed changes. Synchronization errors, known unfinished catch-up and MaxStaleness block the check.

func (*JournalReader) EnforceContext added in v0.0.2

func (reader *JournalReader) EnforceContext(ctx context.Context, values ...interface{}) (bool, error)

EnforceContext synchronizes to a fixed journal position before evaluating the request. Context cancellation or a synchronization error returns false and an error.

func (*JournalReader) Reload added in v0.0.2

func (reader *JournalReader) Reload(ctx context.Context) error

Reload replaces local policies and the cursor with a fresh, consistent database snapshot.

func (*JournalReader) Run added in v0.0.2

func (reader *JournalReader) Run(ctx context.Context) error

Run maintains retention and enables notifications or polling according to its options. Strict checks do not require Run and continue working after it stops.

func (*JournalReader) Status added in v0.0.2

func (reader *JournalReader) Status() JournalStatus

Status returns the current cursor, synchronization time and errors.

func (*JournalReader) Sync added in v0.0.2

func (reader *JournalReader) Sync(ctx context.Context) error

Sync applies complete journal transactions or reloads a full snapshot for large deltas or missing history. A failed attempt leaves the cursor unchanged and suspends authorization until recovery.

type JournalReaderOptions added in v0.0.2

type JournalReaderOptions struct {
	// Listen enables notification-driven synchronization in Run through a dedicated pgdriver connection.
	Listen bool
	// PollInterval enables background synchronization when positive. Zero disables polling.
	PollInterval time.Duration
	// Timeout bounds database synchronization, including waiting for another updater.
	Timeout time.Duration
	// MaxStaleness limits the age of policies used by EnforceCached.
	MaxStaleness time.Duration
	// BatchLimit limits journal transactions per step. Large deltas or missing history reload a full snapshot.
	BatchLimit int
	// Configure runs on each new enforcer before its role links are built.
	Configure func(*casbin.Enforcer) error
}

JournalReaderOptions controls synchronization and explicitly cached checks.

type JournalStatus added in v0.0.2

type JournalStatus struct {
	Revision     int64
	Head         int64
	LastSync     time.Time
	Error        error
	CleanupError error
	Listening    bool
	ListenError  error
}

JournalStatus describes the last synchronization attempt.

type MatcherOptions

type MatcherOptions struct {
	SchemaName string
	TableName  string
	ID         string
	PType      string
	V0         string
	V1         string
	V2         string
	V3         string
	V4         string
	V5         string
}

MatcherOptions is for matching user defined columns to canonical Casbin columns

Directories

Path Synopsis
examples
custom_names command
journal command

Jump to

Keyboard shortcuts

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