Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ func main() {
}
```

## Filtered Policies

```go
import "gopkg.in/mgo.v2/bson"

// This adapter also implements the FilteredAdapter interface. This allows for
// efficent, scalable enforcement of very large policies:
filter := &bson.M{"v0": "alice"}
e.LoadFilteredPolicy(filter)

// The loaded policy is now a subset of the policy in storage, containing only
// the policy lines that match the provided filter. This filter should be a
// valid MongoDB selector using BSON. A filtered policy cannot be saved.
```

## Getting Help

- [Casbin](https://github.com/casbin/casbin)
Expand Down
30 changes: 29 additions & 1 deletion adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package mongodbadapter

import (
"errors"
"runtime"

"github.com/casbin/casbin/model"
Expand All @@ -38,6 +39,7 @@ type adapter struct {
url string
session *mgo.Session
collection *mgo.Collection
filtered bool
}

// finalizer is the destructor for adapter.
Expand All @@ -59,6 +61,13 @@ func NewAdapter(url string) persist.Adapter {
return a
}

// NewFilteredAdapter is the constructor for FilteredAdapter. Behavior is
// otherwise indentical to the NewAdapter function.
func NewFilteredAdapter(url string) persist.FilteredAdapter {
// The adapter already supports the new interface, it just needs to be retyped.
return NewAdapter(url).(*adapter)
}

func (a *adapter) open() {
dI, err := mgo.ParseURL(a.url)
if err != nil {
Expand Down Expand Up @@ -156,15 +165,31 @@ LineEnd:

// LoadPolicy loads policy from database.
func (a *adapter) LoadPolicy(model model.Model) error {
return a.LoadFilteredPolicy(model, nil)
}

// LoadFilteredPolicy loads matching policy lines from database. If not nil,
// the filter must be a valid MongoDB selector.
func (a *adapter) LoadFilteredPolicy(model model.Model, filter interface{}) error {
if filter == nil {
a.filtered = false
} else {
a.filtered = true
}
line := CasbinRule{}
iter := a.collection.Find(nil).Iter()
iter := a.collection.Find(filter).Iter()
for iter.Next(&line) {
loadPolicyLine(line, model)
}

return iter.Close()
}

// IsFiltered returns true if the loaded policy has been filtered.
func (a *adapter) IsFiltered() bool {
return a.filtered
}

func savePolicyLine(ptype string, rule []string) CasbinRule {
line := CasbinRule{
PType: ptype,
Expand Down Expand Up @@ -194,6 +219,9 @@ func savePolicyLine(ptype string, rule []string) CasbinRule {

// SavePolicy saves policy to database.
func (a *adapter) SavePolicy(model model.Model) error {
if a.filtered {
return errors.New("cannot save a filtered policy")
}
if err := a.dropTable(); err != nil {
return err
}
Expand Down
45 changes: 45 additions & 0 deletions adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/casbin/casbin"
"github.com/casbin/casbin/util"
"gopkg.in/mgo.v2/bson"
)

var testDbURL = os.Getenv("TEST_MONGODB_URL")
Expand Down Expand Up @@ -137,6 +138,50 @@ func TestAdapter(t *testing.T) {
testGetPolicy(t, e, [][]string{})
}

func TestFilteredAdapter(t *testing.T) {
// Now the DB has policy, so we can provide a normal use case.
// Create an adapter and an enforcer.
// NewEnforcer() will load the policy automatically.
a := NewAdapter(getDbURL())
e := casbin.NewEnforcer("examples/rbac_model.conf", a)

// Load filtered policies from the database.
e.AddPolicy("alice", "data1", "write")
e.AddPolicy("bob", "data2", "write")
// Reload the filtered policy from the storage.
filter := &bson.M{"v0": "bob"}
if err := e.LoadFilteredPolicy(filter); err != nil {
t.Errorf("Expected LoadFilteredPolicy() to be successful; got %v", err)
}
// Only bob's policy should have been loaded
testGetPolicy(t, e, [][]string{{"bob", "data2", "write"}})

// Verify that alice's policy remains intact in the database.
filter = &bson.M{"v0": "alice"}
if err := e.LoadFilteredPolicy(filter); err != nil {
t.Errorf("Expected LoadFilteredPolicy() to be successful; got %v", err)
}
// Only alice's policy should have been loaded,
testGetPolicy(t, e, [][]string{{"alice", "data1", "write"}})

// Test safe handling of SavePolicy when using filtered policies.
if err := e.SavePolicy(); err == nil {
t.Errorf("Expected SavePolicy() to fail for a filtered policy")
}
if err := e.LoadPolicy(); err != nil {
t.Errorf("Expected LoadPolicy() to be successful; got %v", err)
}
if err := e.SavePolicy(); err != nil {
t.Errorf("Expected SavePolicy() to be successful; got %v", err)
}

e.RemoveFilteredPolicy(2, "write")
if err := e.LoadPolicy(); err != nil {
t.Errorf("Expected LoadPolicy() to be successful; got %v", err)
}
testGetPolicy(t, e, [][]string{})
}

func TestNewAdapterWithInvalidURL(t *testing.T) {
defer func() {
if r := recover(); r == nil {
Expand Down