Skip to content

Commit

Permalink
#614 add casbin for authorization (#787)
Browse files Browse the repository at this point in the history
* add casbin for authorization

* rebase commit, tidy up doc sentences, 80 col, refactor if enforcer section

* add punctuation on casbin doc
  • Loading branch information
suekto-andreas authored and peterbourgon committed Nov 8, 2018
1 parent 33fad1e commit da7ac23
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 0 deletions.
65 changes: 65 additions & 0 deletions auth/casbin/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package casbin

import (
"context"
"errors"

stdcasbin "github.com/casbin/casbin"
"github.com/go-kit/kit/endpoint"
)

type contextKey string

const (
// CasbinModelContextKey holds the key to store the access control model
// in context, it can be a path to configuration file or a casbin/model
// Model.
CasbinModelContextKey contextKey = "CasbinModel"

// CasbinPolicyContextKey holds the key to store the access control policy
// in context, it can be a path to policy file or an implementation of
// casbin/persist Adapter interface.
CasbinPolicyContextKey contextKey = "CasbinPolicy"

// CasbinEnforcerContextKey holds the key to retrieve the active casbin
// Enforcer.
CasbinEnforcerContextKey contextKey = "CasbinEnforcer"
)

var (
// ErrModelContextMissing denotes a casbin model was not passed into
// the parsing of middleware's context.
ErrModelContextMissing = errors.New("CasbinModel is required in context")

// ErrPolicyContextMissing denotes a casbin policy was not passed into
// the parsing of middleware's context.
ErrPolicyContextMissing = errors.New("CasbinPolicy is required in context")

// ErrUnauthorized denotes the subject is not authorized to do the action
// intended on the given object, based on the context model and policy.
ErrUnauthorized = errors.New("Unauthorized Access")
)

// NewEnforcer checks whether the subject is authorized to do the specified
// action on the given object. If a valid access control model and policy
// is given, then the generated casbin Enforcer is stored in the context
// with CasbinEnforcer as the key.
func NewEnforcer(
subject string, object interface{}, action string,
) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (
response interface{}, err error,
) {
casbinModel := ctx.Value(CasbinModelContextKey)
casbinPolicy := ctx.Value(CasbinPolicyContextKey)

enforcer := stdcasbin.NewEnforcer(casbinModel, casbinPolicy)
ctx = context.WithValue(ctx, CasbinEnforcerContextKey, enforcer)
if !enforcer.Enforce(subject, object, action) {
return nil, ErrUnauthorized
}
return next(ctx, request)
}
}
}
55 changes: 55 additions & 0 deletions auth/casbin/middleware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package casbin

import (
"context"
"testing"

stdcasbin "github.com/casbin/casbin"
fileadapter "github.com/casbin/casbin/persist/file-adapter"
)

func TestStructBaseContext(t *testing.T) {
e := func(ctx context.Context, i interface{}) (interface{}, error) { return ctx, nil }

m := stdcasbin.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("e", "e", "some(where (p.eft == allow))")
m.AddDef("m", "m", "r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)")

a := fileadapter.NewAdapter("testdata/keymatch_policy.csv")

ctx := context.WithValue(context.Background(), CasbinModelContextKey, m)
ctx = context.WithValue(ctx, CasbinPolicyContextKey, a)

// positive case
middleware := NewEnforcer("alice", "/alice_data/resource1", "GET")(e)
ctx1, err := middleware(ctx, struct{}{})
if err != nil {
t.Fatalf("Enforcer returned error: %s", err)
}
_, ok := ctx1.(context.Context).Value(CasbinEnforcerContextKey).(*stdcasbin.Enforcer)
if !ok {
t.Fatalf("context should contains the active enforcer")
}

// negative case
middleware = NewEnforcer("alice", "/alice_data/resource2", "POST")(e)
_, err = middleware(ctx, struct{}{})
if err == nil {
t.Fatalf("Enforcer should return error")
}
}

func TestFileBaseContext(t *testing.T) {
e := func(ctx context.Context, i interface{}) (interface{}, error) { return ctx, nil }
ctx := context.WithValue(context.Background(), CasbinModelContextKey, "testdata/basic_model.conf")
ctx = context.WithValue(ctx, CasbinPolicyContextKey, "testdata/basic_policy.csv")

// positive case
middleware := NewEnforcer("alice", "data1", "read")(e)
_, err := middleware(ctx, struct{}{})
if err != nil {
t.Fatalf("Enforcer returned error: %s", err)
}
}
11 changes: 11 additions & 0 deletions auth/casbin/testdata/basic_model.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[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
2 changes: 2 additions & 0 deletions auth/casbin/testdata/basic_policy.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
p, alice, data1, read
p, bob, data2, write
7 changes: 7 additions & 0 deletions auth/casbin/testdata/keymatch_policy.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
p, alice, /alice_data/*, GET
p, alice, /alice_data/resource1, POST

p, bob, /alice_data/resource2, GET
p, bob, /bob_data/*, POST

p, cathy, /cathy_data, (GET)|(POST)

0 comments on commit da7ac23

Please sign in to comment.