Bootstrap copied BFF and local accounts with public Sendico contracts (#34)

This commit is contained in:
Stephan D committed 2026-09-12 13:37:48 +02:00
1 parent 67dc5b5b1e
commit 7062672a87
556 files changed
+42291 -777

No files matched your search

@@ -0,0 +1,357 @@
package mongoadapter
import (
"context"
"git.sendico.io/tech/sxchnge/bff/pkg/merrors"
"time"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo"
"go.mongodb.org/mongo-driver/v2/mongo/options"
)
const (
defaultDatabaseName = "casbin"
defaultCollectionName = "casbin_rule"
defaultTimeout = 30 * time.Second
)
var (
_ persist.BatchAdapter = (*Adapter)(nil)
_ persist.FilteredAdapter = (*Adapter)(nil)
_ persist.UpdatableAdapter = (*Adapter)(nil)
)
type Config struct {
DatabaseName string
CollectionName string
Timeout time.Duration
IsFiltered bool
}
type Rule struct {
PType string
V0 string
V1 string
V2 string
V3 string
V4 string
V5 string
}
type Adapter struct {
collection *mongo.Collection
timeout time.Duration
filtered bool
}
func New(client *mongo.Client, config *Config) (*Adapter, error) {
if config == nil {
config = &Config{}
}
databaseName := config.DatabaseName
if databaseName == "" {
databaseName = defaultDatabaseName
}
collectionName := config.CollectionName
if collectionName == "" {
collectionName = defaultCollectionName
}
timeout := config.Timeout
if timeout == 0 {
timeout = defaultTimeout
}
adapter := &Adapter{
collection: client.Database(databaseName).Collection(collectionName),
timeout: timeout,
filtered: config.IsFiltered,
}
if err := adapter.prepareIndexes(); err != nil {
return nil, err
}
return adapter, nil
}
func (a *Adapter) prepareIndexes() error {
keys := bson.D{
{Key: "ptype", Value: 1},
{Key: "v0", Value: 1},
{Key: "v1", Value: 1},
{Key: "v2", Value: 1},
{Key: "v3", Value: 1},
{Key: "v4", Value: 1},
{Key: "v5", Value: 1},
}
_, err := a.collection.Indexes().CreateOne(
context.Background(),
mongo.IndexModel{
Keys: keys,
Options: options.Index().SetUnique(true),
},
)
return err
}
func (a *Adapter) withTimeout() (context.Context, context.CancelFunc) {
return context.WithTimeout(context.Background(), a.timeout)
}
func loadPolicyLine(line Rule, m model.Model) error {
policy := line.policyWithType()
for len(policy) > 0 && policy[len(policy)-1] == "" {
policy = policy[:len(policy)-1]
}
if len(policy) == 0 {
return nil
}
return persist.LoadPolicyArray(policy, m)
}
func (r Rule) policyWithType() []string {
return []string{r.PType, r.V0, r.V1, r.V2, r.V3, r.V4, r.V5}
}
func (r Rule) policyValues() []string {
values := []string{r.V0, r.V1, r.V2, r.V3, r.V4, r.V5}
for len(values) > 0 && values[len(values)-1] == "" {
values = values[:len(values)-1]
}
return values
}
func ruleFromPolicy(ptype string, policy []string) Rule {
rule := Rule{PType: ptype}
if len(policy) > 0 {
rule.V0 = policy[0]
}
if len(policy) > 1 {
rule.V1 = policy[1]
}
if len(policy) > 2 {
rule.V2 = policy[2]
}
if len(policy) > 3 {
rule.V3 = policy[3]
}
if len(policy) > 4 {
rule.V4 = policy[4]
}
if len(policy) > 5 {
rule.V5 = policy[5]
}
return rule
}
func rulesFromPolicies(ptype string, policies [][]string) []Rule {
rules := make([]Rule, 0, len(policies))
for _, policy := range policies {
rules = append(rules, ruleFromPolicy(ptype, policy))
}
return rules
}
func rulesAsDocuments(rules []Rule) []any {
docs := make([]any, 0, len(rules))
for _, rule := range rules {
docs = append(docs, rule)
}
return docs
}
func selectorForPolicy(ptype string, fieldIndex int, fieldValues ...string) bson.M {
selector := bson.M{"ptype": ptype}
fields := []string{"v0", "v1", "v2", "v3", "v4", "v5"}
for i, fieldValue := range fieldValues {
if fieldValue == "" {
continue
}
field := fieldIndex + i
if field < 0 || field >= len(fields) {
continue
}
selector[fields[field]] = fieldValue
}
return selector
}
func (a *Adapter) LoadPolicy(m model.Model) error {
return a.LoadFilteredPolicy(m, nil)
}
func (a *Adapter) LoadFilteredPolicy(m model.Model, filter any) error {
if filter == nil {
a.filtered = false
filter = bson.D{}
} else {
a.filtered = true
}
ctx, cancel := a.withTimeout()
defer cancel()
cursor, err := a.collection.Find(ctx, filter)
if err != nil {
return err
}
defer func() {
_ = cursor.Close(ctx)
}()
for cursor.Next(ctx) {
var line Rule
if err := cursor.Decode(&line); err != nil {
return err
}
if err := loadPolicyLine(line, m); err != nil {
return err
}
}
return cursor.Err()
}
func (a *Adapter) IsFiltered() bool {
return a.filtered
}
func (a *Adapter) SavePolicy(m model.Model) error {
if a.filtered {
return merrors.Define("cannot save a filtered policy")
}
ctx, cancel := a.withTimeout()
defer cancel()
if _, err := a.collection.DeleteMany(ctx, bson.D{}); err != nil {
return err
}
var docs []any
for ptype, ast := range m["p"] {
docs = append(docs, rulesAsDocuments(rulesFromPolicies(ptype, ast.Policy))...)
}
for ptype, ast := range m["g"] {
docs = append(docs, rulesAsDocuments(rulesFromPolicies(ptype, ast.Policy))...)
}
if len(docs) == 0 {
return nil
}
_, err := a.collection.InsertMany(ctx, docs)
return err
}
func (a *Adapter) AddPolicy(_ string, ptype string, policy []string) error {
ctx, cancel := a.withTimeout()
defer cancel()
_, err := a.collection.InsertOne(ctx, ruleFromPolicy(ptype, policy))
return err
}
func (a *Adapter) AddPolicies(_ string, ptype string, policies [][]string) error {
if len(policies) == 0 {
return nil
}
ctx, cancel := a.withTimeout()
defer cancel()
_, err := a.collection.InsertMany(ctx, rulesAsDocuments(rulesFromPolicies(ptype, policies)))
return err
}
func (a *Adapter) RemovePolicy(_ string, ptype string, policy []string) error {
ctx, cancel := a.withTimeout()
defer cancel()
_, err := a.collection.DeleteOne(ctx, ruleFromPolicy(ptype, policy))
return err
}
func (a *Adapter) RemovePolicies(_ string, ptype string, policies [][]string) error {
ctx, cancel := a.withTimeout()
defer cancel()
for _, rule := range rulesFromPolicies(ptype, policies) {
if _, err := a.collection.DeleteOne(ctx, rule); err != nil {
return err
}
}
return nil
}
func (a *Adapter) RemoveFilteredPolicy(_ string, ptype string, fieldIndex int, fieldValues ...string) error {
ctx, cancel := a.withTimeout()
defer cancel()
_, err := a.collection.DeleteMany(ctx, selectorForPolicy(ptype, fieldIndex, fieldValues...))
return err
}
func (a *Adapter) UpdatePolicy(_ string, ptype string, oldPolicy, newPolicy []string) error {
ctx, cancel := a.withTimeout()
defer cancel()
_, err := a.collection.ReplaceOne(ctx, ruleFromPolicy(ptype, oldPolicy), ruleFromPolicy(ptype, newPolicy))
return err
}
func (a *Adapter) UpdatePolicies(_ string, ptype string, oldPolicies, newPolicies [][]string) error {
if len(oldPolicies) != len(newPolicies) {
return merrors.Define("old and new policy counts differ")
}
ctx, cancel := a.withTimeout()
defer cancel()
for i := range oldPolicies {
if _, err := a.collection.ReplaceOne(ctx, ruleFromPolicy(ptype, oldPolicies[i]), ruleFromPolicy(ptype, newPolicies[i])); err != nil {
return err
}
}
return nil
}
func (a *Adapter) UpdateFilteredPolicies(_ string, ptype string, newPolicies [][]string, fieldIndex int, fieldValues ...string) ([][]string, error) {
ctx, cancel := a.withTimeout()
defer cancel()
selector := selectorForPolicy(ptype, fieldIndex, fieldValues...)
cursor, err := a.collection.Find(ctx, selector)
if err != nil {
return nil, err
}
var oldPolicies [][]string
for cursor.Next(ctx) {
var line Rule
if err := cursor.Decode(&line); err != nil {
_ = cursor.Close(ctx)
return nil, err
}
oldPolicies = append(oldPolicies, line.policyValues())
}
if err := cursor.Close(ctx); err != nil {
return nil, err
}
if err := cursor.Err(); err != nil {
return nil, err
}
if _, err := a.collection.DeleteMany(ctx, selector); err != nil {
return nil, err
}
if len(newPolicies) == 0 {
return oldPolicies, nil
}
if _, err := a.collection.InsertMany(ctx, rulesAsDocuments(rulesFromPolicies(ptype, newPolicies))); err != nil {
return nil, err
}
return oldPolicies, nil
}
@@ -0,0 +1,68 @@
package mongoadapter
import (
"reflect"
"testing"
"github.com/casbin/casbin/v3/model"
"go.mongodb.org/mongo-driver/v2/bson"
)
func TestRuleFromPolicyTrimsOnlyTrailingEmptyValues(t *testing.T) {
rule := ruleFromPolicy("p", []string{"role", "org", "", "object", ""})
if got, want := rule.PType, "p"; got != want {
t.Fatalf("PType = %q, want %q", got, want)
}
if got, want := rule.policyValues(), []string{"role", "org", "", "object"}; !reflect.DeepEqual(got, want) {
t.Fatalf("policyValues() = %#v, want %#v", got, want)
}
}
func TestSelectorForPolicySkipsEmptyAndOutOfRangeFields(t *testing.T) {
got := selectorForPolicy("p", 1, "role", "", "object", "read", "ignored", "ignored")
want := bson.M{
"ptype": "p",
"v1": "role",
"v3": "object",
"v4": "read",
"v5": "ignored",
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("selectorForPolicy() = %#v, want %#v", got, want)
}
}
func TestLoadPolicyLineAddsPolicyToModel(t *testing.T) {
m, err := model.NewModelFromString(`
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
`)
if err != nil {
t.Fatalf("NewModelFromString() error = %v", err)
}
err = loadPolicyLine(Rule{PType: "p", V0: "alice", V1: "data1", V2: "read"}, m)
if err != nil {
t.Fatalf("loadPolicyLine() error = %v", err)
}
got, err := m.GetPolicy("p", "p")
if err != nil {
t.Fatalf("GetPolicy() error = %v", err)
}
want := [][]string{{"alice", "data1", "read"}}
if !reflect.DeepEqual(got, want) {
t.Fatalf("GetPolicy() = %#v, want %#v", got, want)
}
}