Skip to content

Commit

Permalink
feat: Contextadapter (#59)
Browse files Browse the repository at this point in the history
* feat: context adapter

* feat: context adapter

* feat: context adapter
  • Loading branch information
PokIsemaine committed Sep 6, 2023
1 parent 047522b commit 1089d55
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 37 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,21 @@ func main() {
}
```

## Context Adapter

`xormadapter` supports adapter with context, the following is a timeout control implemented using context

```go
ca, _ := NewContextAdapter("mysql", "root:@tcp(127.0.0.1:3306)/", "casbin")
// Limited time 300s
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Microsecond)
defer cancel()
err := ca.AddPolicyCtx(ctx, "p", "p", []string{"alice", "data1", "read"})
if err != nil {
panic(err)
}
```

## Getting Help

- [Casbin](https://github.com/casbin/casbin)
Expand Down
86 changes: 86 additions & 0 deletions context_adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2023 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package xormadapter

import (
"context"

"github.com/casbin/casbin/v2/model"
)

type ContextAdapter struct {
*Adapter
}

func NewContextAdapter(driverName string, dataSourceName string, dbSpecified ...bool) (*ContextAdapter, error) {
a, err := NewAdapter(driverName, dataSourceName, dbSpecified...)
return &ContextAdapter{
a,
}, err
}

// executeWithContext is a helper function to execute a function with context and return the result or error.
func executeWithContext(ctx context.Context, fn func() error) error {
done := make(chan error)

go func() {
done <- fn()
}()

select {
case <-ctx.Done():
return ctx.Err()
case err := <-done:
return err
}
}

// LoadPolicyCtx loads all policy rules from the storage with context.
func (ca *ContextAdapter) LoadPolicyCtx(ctx context.Context, model model.Model) error {
return executeWithContext(ctx, func() error {
return ca.LoadPolicy(model)
})
}

// SavePolicyCtx saves all policy rules to the storage with context.
func (ca *ContextAdapter) SavePolicyCtx(ctx context.Context, model model.Model) error {
return executeWithContext(ctx, func() error {
return ca.SavePolicy(model)
})
}

// AddPolicyCtx adds a policy rule to the storage with context.
// This is part of the Auto-Save feature.
func (ca *ContextAdapter) AddPolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error {
return executeWithContext(ctx, func() error {
return ca.AddPolicy(sec, ptype, rule)
})
}

// RemovePolicyCtx removes a policy rule from the storage with context.
// This is part of the Auto-Save feature.
func (ca *ContextAdapter) RemovePolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error {
return executeWithContext(ctx, func() error {
return ca.RemovePolicy(sec, ptype, rule)
})
}

// RemoveFilteredPolicyCtx removes policy rules that match the filter from the storage with context.
// This is part of the Auto-Save feature.
func (ca *ContextAdapter) RemoveFilteredPolicyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues ...string) error {
return executeWithContext(ctx, func() error {
return ca.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
})
}
152 changes: 152 additions & 0 deletions context_adapter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright 2023 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package xormadapter

import (
"context"
"testing"
"time"

"github.com/agiledragon/gomonkey/v2"
"github.com/casbin/casbin/v2"
"github.com/stretchr/testify/assert"
"xorm.io/xorm"
)

func mockExecuteWithContextTimeOut(ctx context.Context, fn func() error) error {
done := make(chan error)
go func() {
time.Sleep(500 * time.Microsecond)
done <- fn()
}()

select {
case <-ctx.Done():
return ctx.Err()
case err := <-done:
return err
}
}

func clearDBPolicy() (*casbin.Enforcer, *ContextAdapter) {
ca, err := NewContextAdapter("mysql", "root:@tcp(127.0.0.1:3306)/")
if err != nil {
panic(err)
}

e, err := casbin.NewEnforcer("examples/rbac_model.conf", ca)
if err != nil {
panic(err)
}

e.ClearPolicy()
_ = e.SavePolicy()

return e, ca

return e, ca
}

func TestContextAdapter_LoadPolicyCtx(t *testing.T) {
e, ca := clearDBPolicy()

engine, _ := xorm.NewEngine("mysql", "root:@tcp(127.0.0.1:3306)/casbin")
policy := &CasbinRule{
Ptype: "p",
V0: "alice",
V1: "data1",
V2: "read",
}
_, err := engine.Insert(policy)
if err != nil {
panic(err)
}

assert.NoError(t, ca.LoadPolicyCtx(context.Background(), e.GetModel()))
e, _ = casbin.NewEnforcer(e.GetModel(), ca)
testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}})

var p = gomonkey.ApplyFunc(executeWithContext, mockExecuteWithContextTimeOut)
defer p.Reset()
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Microsecond)
defer cancel()

assert.EqualError(t, ca.LoadPolicyCtx(ctx, e.GetModel()), "context deadline exceeded")
}

func TestContextAdapter_SavePolicyCtx(t *testing.T) {
e, ca := clearDBPolicy()

e.EnableAutoSave(false)
_, _ = e.AddPolicy("alice", "data1", "read")
assert.NoError(t, ca.SavePolicyCtx(context.Background(), e.GetModel()))
_ = e.LoadPolicy()
testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}})

var p = gomonkey.ApplyFunc(executeWithContext, mockExecuteWithContextTimeOut)
defer p.Reset()
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Microsecond)
defer cancel()
assert.EqualError(t, ca.SavePolicyCtx(ctx, e.GetModel()), "context deadline exceeded")
// Sleep, waiting for the completion of the transaction commit
time.Sleep(2 * time.Second)
}

func TestContextAdapter_AddPolicyCtx(t *testing.T) {
e, ca := clearDBPolicy()

assert.NoError(t, ca.AddPolicyCtx(context.Background(), "p", "p", []string{"alice", "data1", "read"}))
_ = e.LoadPolicy()
testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}})

var p = gomonkey.ApplyFunc(executeWithContext, mockExecuteWithContextTimeOut)
defer p.Reset()
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Microsecond)
defer cancel()
assert.EqualError(t, ca.AddPolicyCtx(ctx, "p", "p", []string{"alice", "data1", "read"}), "context deadline exceeded")
}

func TestContextAdapter_RemovePolicyCtx(t *testing.T) {
e, ca := clearDBPolicy()

_ = ca.AddPolicy("p", "p", []string{"alice", "data1", "read"})
_ = ca.AddPolicy("p", "p", []string{"alice", "data2", "read"})
assert.NoError(t, ca.RemovePolicyCtx(context.Background(), "p", "p", []string{"alice", "data1", "read"}))
_ = e.LoadPolicy()
testGetPolicy(t, e, [][]string{{"alice", "data2", "read"}})

var p = gomonkey.ApplyFunc(executeWithContext, mockExecuteWithContextTimeOut)
defer p.Reset()
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Microsecond)
defer cancel()
assert.EqualError(t, ca.RemovePolicyCtx(ctx, "p", "p", []string{"alice", "data1", "read"}), "context deadline exceeded")
}

func TestContextAdapter_RemoveFilteredPolicyCtx(t *testing.T) {
e, ca := clearDBPolicy()

_ = ca.AddPolicy("p", "p", []string{"alice", "data1", "read"})
_ = ca.AddPolicy("p", "p", []string{"alice", "data1", "write"})
_ = ca.AddPolicy("p", "p", []string{"alice", "data2", "read"})
assert.NoError(t, ca.RemoveFilteredPolicyCtx(context.Background(), "p", "p", 1, "data1"))
_ = e.LoadPolicy()
testGetPolicy(t, e, [][]string{{"alice", "data2", "read"}})

var p = gomonkey.ApplyFunc(executeWithContext, mockExecuteWithContextTimeOut)
defer p.Reset()
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Microsecond)
defer cancel()
assert.EqualError(t, ca.RemoveFilteredPolicyCtx(ctx, "p", "p", 1, "data1"), "context deadline exceeded")
}
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ module github.com/casbin/xorm-adapter/v3
go 1.12

require (
github.com/PuerkitoBio/goquery v1.5.1 // indirect
github.com/casbin/casbin/v2 v2.28.3
github.com/agiledragon/gomonkey/v2 v2.10.1
github.com/casbin/casbin/v2 v2.77.2
github.com/go-sql-driver/mysql v1.6.0
github.com/lib/pq v1.10.2
github.com/stretchr/testify v1.7.0
xorm.io/xorm v1.3.2
)
Loading

0 comments on commit 1089d55

Please sign in to comment.