From c54a1ec72ab442ef17a0ffd1179df337d86d64b8 Mon Sep 17 00:00:00 2001 From: andreas Date: Tue, 6 Nov 2018 21:31:21 +0800 Subject: [PATCH 1/3] add casbin for authorization --- auth/casbin/middleware.go | 49 +++++++++++++++++++++ auth/casbin/middleware_test.go | 55 ++++++++++++++++++++++++ auth/casbin/testdata/basic_model.conf | 11 +++++ auth/casbin/testdata/basic_policy.csv | 2 + auth/casbin/testdata/keymatch_policy.csv | 7 +++ 5 files changed, 124 insertions(+) create mode 100644 auth/casbin/middleware.go create mode 100644 auth/casbin/middleware_test.go create mode 100644 auth/casbin/testdata/basic_model.conf create mode 100644 auth/casbin/testdata/basic_policy.csv create mode 100644 auth/casbin/testdata/keymatch_policy.csv diff --git a/auth/casbin/middleware.go b/auth/casbin/middleware.go new file mode 100644 index 000000000..be56e43e9 --- /dev/null +++ b/auth/casbin/middleware.go @@ -0,0 +1,49 @@ +package casbin + +import ( + "context" + "errors" + + stdcasbin "github.com/casbin/casbin" + "github.com/go-kit/kit/endpoint" +) + +var ( + // ErrModelContextMissing required CasbinModel + ErrModelContextMissing = errors.New("CasbinModel is required in context") + // ErrPolicyContextMissing required CasbinPolicy + ErrPolicyContextMissing = errors.New("CasbinPolicy is required in context") + // ErrUnauthorized describes unauthorized access + ErrUnauthorized = errors.New("Unauthorized Access") +) + +type contextKey string + +const ( + // CasbinModelContextKey key to store the model, can be a file or casbin model + // a model file e.g. "path/to/basic_model.conf" + CasbinModelContextKey contextKey = "CasbinModel" + // CasbinPolicyContextKey key to store the policy, can be a file or casbin policy adapter + // a policy file e.g. "path/to/basic_policy.csv" + CasbinPolicyContextKey contextKey = "CasbinPolicy" + // CasbinEnforcerContextKey key where the active enforcer can be retrieved + CasbinEnforcerContextKey contextKey = "CasbinEnforcer" +) + +// NewEnforcer installs casbin enforcer into the context +// while also checking the authorization for the corresponding subject, object, and action +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) == false { + return nil, ErrUnauthorized + } + return next(ctx, request) + } + } +} diff --git a/auth/casbin/middleware_test.go b/auth/casbin/middleware_test.go new file mode 100644 index 000000000..5dbb003df --- /dev/null +++ b/auth/casbin/middleware_test.go @@ -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) + } +} diff --git a/auth/casbin/testdata/basic_model.conf b/auth/casbin/testdata/basic_model.conf new file mode 100644 index 000000000..dc6da8136 --- /dev/null +++ b/auth/casbin/testdata/basic_model.conf @@ -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 \ No newline at end of file diff --git a/auth/casbin/testdata/basic_policy.csv b/auth/casbin/testdata/basic_policy.csv new file mode 100644 index 000000000..57aaa9760 --- /dev/null +++ b/auth/casbin/testdata/basic_policy.csv @@ -0,0 +1,2 @@ +p, alice, data1, read +p, bob, data2, write \ No newline at end of file diff --git a/auth/casbin/testdata/keymatch_policy.csv b/auth/casbin/testdata/keymatch_policy.csv new file mode 100644 index 000000000..d6e9b7d40 --- /dev/null +++ b/auth/casbin/testdata/keymatch_policy.csv @@ -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) \ No newline at end of file From 8984c85699bfbdedab7e6a792c6a3c68ac7f2bbb Mon Sep 17 00:00:00 2001 From: andreas Date: Wed, 7 Nov 2018 21:52:23 +0800 Subject: [PATCH 2/3] rebase commit, tidy up doc sentences, 80 col, refactor if enforcer section --- auth/casbin/middleware.go | 54 +++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/auth/casbin/middleware.go b/auth/casbin/middleware.go index be56e43e9..227a7169f 100644 --- a/auth/casbin/middleware.go +++ b/auth/casbin/middleware.go @@ -8,39 +8,55 @@ import ( "github.com/go-kit/kit/endpoint" ) -var ( - // ErrModelContextMissing required CasbinModel - ErrModelContextMissing = errors.New("CasbinModel is required in context") - // ErrPolicyContextMissing required CasbinPolicy - ErrPolicyContextMissing = errors.New("CasbinPolicy is required in context") - // ErrUnauthorized describes unauthorized access - ErrUnauthorized = errors.New("Unauthorized Access") -) - type contextKey string const ( - // CasbinModelContextKey key to store the model, can be a file or casbin model - // a model file e.g. "path/to/basic_model.conf" + // 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 key to store the policy, can be a file or casbin policy adapter - // a policy file e.g. "path/to/basic_policy.csv" + + // 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 key where the active enforcer can be retrieved + + // CasbinEnforcerContextKey holds the key to retrieve the active casbin + // Enforcer CasbinEnforcerContextKey contextKey = "CasbinEnforcer" ) -// NewEnforcer installs casbin enforcer into the context -// while also checking the authorization for the corresponding subject, object, and action -func NewEnforcer(subject string, object interface{}, action string) endpoint.Middleware { +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 controlmodel 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) { + 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) == false { + if !enforcer.Enforce(subject, object, action) { return nil, ErrUnauthorized } return next(ctx, request) From bf51e5ee889a96124f7d81177a53b9bb5ce56d64 Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 8 Nov 2018 04:57:54 +0800 Subject: [PATCH 3/3] add punctuation on casbin doc --- auth/casbin/middleware.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/auth/casbin/middleware.go b/auth/casbin/middleware.go index 227a7169f..fdbf6dc31 100644 --- a/auth/casbin/middleware.go +++ b/auth/casbin/middleware.go @@ -13,37 +13,37 @@ 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 + // 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 + // casbin/persist Adapter interface. CasbinPolicyContextKey contextKey = "CasbinPolicy" // CasbinEnforcerContextKey holds the key to retrieve the active casbin - // Enforcer + // Enforcer. CasbinEnforcerContextKey contextKey = "CasbinEnforcer" ) var ( // ErrModelContextMissing denotes a casbin model was not passed into - // the parsing of middleware's context + // 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 + // 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 + // 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 controlmodel and policy +// 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 +// with CasbinEnforcer as the key. func NewEnforcer( subject string, object interface{}, action string, ) endpoint.Middleware {