From c6a6d96e972d533167ed98fd80cfa589932813b5 Mon Sep 17 00:00:00 2001 From: L2ncE Date: Sun, 5 Feb 2023 20:41:34 +0800 Subject: [PATCH 1/3] feat: add casbin extension --- LICENSE-APACHE => LICENSE | 0 README.md | 91 +++++++- casbin.go | 183 +++++++++++++++ casbin_test.go | 322 +++++++++++++++++++++++++++ example/config/model.conf | 14 ++ example/config/policy.csv | 2 + example/config/policy_read_write.csv | 3 + example/config/policy_user_admin.csv | 4 + example/main.go | 72 ++++++ go.mod | 36 +++ go.sum | 101 +++++++++ licenses/LICENSE-casbin.txt | 201 +++++++++++++++++ licenses/LICENSE-fiber-casbin.txt | 21 ++ licenses/LICENSE-gin-casbin.txt | 21 ++ option.go | 140 ++++++++++++ profile/README.md | 13 -- 16 files changed, 1210 insertions(+), 14 deletions(-) rename LICENSE-APACHE => LICENSE (100%) create mode 100644 casbin.go create mode 100644 casbin_test.go create mode 100644 example/config/model.conf create mode 100644 example/config/policy.csv create mode 100644 example/config/policy_read_write.csv create mode 100644 example/config/policy_user_admin.csv create mode 100644 example/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 licenses/LICENSE-casbin.txt create mode 100644 licenses/LICENSE-fiber-casbin.txt create mode 100644 licenses/LICENSE-gin-casbin.txt create mode 100644 option.go delete mode 100644 profile/README.md diff --git a/LICENSE-APACHE b/LICENSE similarity index 100% rename from LICENSE-APACHE rename to LICENSE diff --git a/README.md b/README.md index a46ae92..f4e9251 100644 --- a/README.md +++ b/README.md @@ -1 +1,90 @@ -# .github \ No newline at end of file +# Casbin (This is a community driven project) + +Casbin is an authorization library that supports access control models like ACL, RBAC, ABAC. + +This repo inspired by [fiber-casbin](https://github.com/gofiber/contrib/tree/main/casbin) and adapted to Hertz. + +## Install + +``` shell +go get github.com/hertz-contrib/casbin +``` + +## Import + +```go +import "github.com/hertz-contrib/casbin" +``` + +## Example + +```go +package main + +import ( + "context" + "log" + + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/app/server" + "github.com/hertz-contrib/casbin" + "github.com/hertz-contrib/sessions" + "github.com/hertz-contrib/sessions/cookie" +) + +func main() { + h := server.Default() + + // Using sessions and casbin. + store := cookie.NewStore([]byte("secret")) + h.Use(sessions.New("session", store)) + auth, err := casbin.NewCasbinMiddleware("example/config/model.conf", "example/config/policy.csv", subjectFromSession) + if err != nil { + log.Fatal(err) + } + + h.POST("/login", func(ctx context.Context, c *app.RequestContext) { + // Verify username and password. + // ... + + // Store current subject in session + session := sessions.Default(c) + session.Set("name", "alice") + err := session.Save() + if err != nil { + log.Fatal(err) + } + c.String(200, "you login successfully") + }) + + h.GET("/book", auth.RequiresPermissions([]string{"book:read"}, casbin.WithLogic(casbin.AND)), func(ctx context.Context, c *app.RequestContext) { + c.String(200, "you read the book successfully") + }) + h.POST("/book", auth.RequiresRoles([]string{"user"}, casbin.WithLogic(casbin.AND)), func(ctx context.Context, c *app.RequestContext) { + c.String(200, "you posted a book successfully") + }) + + h.Spin() +} + +// subjectFromSession get subject from session. +func subjectFromSession(ctx context.Context, c *app.RequestContext) string { + // Get subject from session. + session := sessions.Default(c) + if subject, ok := session.Get("name").(string); !ok { + return "" + } else { + return subject + } +} +``` + +## Options + +| Option | Default | Description | +| ---------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| Logic | `AND` | Logic is the logical operation (AND/OR) used in permission checks in case multiple permissions or roles are specified. | +| PermissionParser | `PermissionParserWithSeparator(":")` | PermissionParserFunc is used for parsing the permission to extract object and action usually. | +| Unauthorized | `func(ctx context.Context, c *app.RequestContext) { c.AbortWithStatus(http.StatusUnauthorized) }` | Unauthorized defines the response body for unauthorized responses. | +| Forbidden | `func(ctx context.Context, c *app.RequestContext) { c.AbortWithStatus(http.StatusForbidden) }` | Forbidden defines the response body for forbidden responses. | + diff --git a/casbin.go b/casbin.go new file mode 100644 index 0000000..2a39b5a --- /dev/null +++ b/casbin.go @@ -0,0 +1,183 @@ +// Copyright 2023 CloudWeGo Authors +// +// 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 casbin + +import ( + "context" + "net/http" + + "github.com/casbin/casbin/v2" + "github.com/cloudwego/hertz/pkg/app" +) + +type Middleware struct { + // Enforcer is the main interface for authorization enforcement and policy management. + enforcer *casbin.Enforcer + // LookupHandler is used to look up current subject in runtime. + // If it can not find anything, just return an empty string. + lookup LookupHandler +} + +// NewCasbinMiddleware returns a new Middleware using Casbin's Enforcer internally. +// +// modelFile is the file path to Casbin model file e.g. path/to/rbac_model.conf. +// adapter can be a file or a DB adapter. +// lookup is a function that looks up the current subject in runtime and returns an empty string if nothing found. +func NewCasbinMiddleware(modelFile string, adapter interface{}, lookup LookupHandler) (*Middleware, error) { + e, err := casbin.NewEnforcer(modelFile, adapter) + if err != nil { + return nil, err + } + + return NewCasbinMiddlewareFromEnforcer(e, lookup) +} + +// NewCasbinMiddlewareFromEnforcer creates from given Enforcer. +func NewCasbinMiddlewareFromEnforcer(e *casbin.Enforcer, lookup LookupHandler) (*Middleware, error) { + if lookup == nil { + return nil, errLookupNil + } + + return &Middleware{ + enforcer: e, + lookup: lookup, + }, nil +} + +// RequiresPermissions tries to find the current subject and determine if the +// subject has the required permissions according to predefined Casbin policies. +func (m *Middleware) RequiresPermissions(permissions []string, opts ...Option) app.HandlerFunc { + // Here we provide default options. + options := NewOptions(opts...) + return func(ctx context.Context, c *app.RequestContext) { + if len(permissions) == 0 { + c.Next(ctx) + return + } + // Look up current subject. + sub := m.lookup(ctx, c) + if sub == "" { + options.Unauthorized(ctx, c) + return + } + // Enforce Casbin policies. + if options.Logic == AND { + // Must pass all tests. + for _, permission := range permissions { + vals := append([]string{sub}, options.PermissionParser(permission)...) + if vals[0] == "" || vals[1] == "" { + // Can not handle any illegal permission strings. + c.AbortWithStatus(http.StatusInternalServerError) + return + } + if ok, err := m.enforcer.Enforce(stringSliceToInterfaceSlice(vals)...); err != nil { + c.AbortWithStatus(http.StatusInternalServerError) + return + } else if !ok { + options.Forbidden(ctx, c) + return + } + } + c.Next(ctx) + return + } else if options.Logic == OR { + // Need to pass at least one test. + for _, permission := range permissions { + values := append([]string{sub}, options.PermissionParser(permission)...) + if values[0] == "" || values[1] == "" { + // Can not handle any illegal permission strings. + c.AbortWithStatus(http.StatusInternalServerError) + return + } + if ok, err := m.enforcer.Enforce(stringSliceToInterfaceSlice(values)...); err != nil { + c.AbortWithStatus(http.StatusInternalServerError) + return + } else if ok { + c.Next(ctx) + return + } + } + options.Forbidden(ctx, c) + return + } + c.Next(ctx) + return + } +} + +// RequiresRoles tries to find the current subject and determine if the +// subject has the required roles according to predefined Casbin policies. +func (m *Middleware) RequiresRoles(requiredRoles []string, opts ...Option) app.HandlerFunc { + // Here we provide default options. + options := NewOptions(opts...) + return func(ctx context.Context, c *app.RequestContext) { + if len(requiredRoles) == 0 { + c.Next(ctx) + return + } + // Look up current subject. + sub := m.lookup(ctx, c) + if sub == "" { + options.Unauthorized(ctx, c) + return + } + actualRoles, err := m.enforcer.GetRolesForUser(sub) + if err != nil { + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + if options.Logic == AND { + // Must have all required roles. + for _, role := range requiredRoles { + if !containsString(actualRoles, role) { + options.Forbidden(ctx, c) + return + } + } + c.Next(ctx) + return + } else if options.Logic == OR { + // Need to have at least one of required roles. + for _, role := range requiredRoles { + if containsString(actualRoles, role) { + c.Next(ctx) + return + } + } + options.Forbidden(ctx, c) + return + } + c.Next(ctx) + return + } +} + +func containsString(s []string, v string) bool { + for _, vv := range s { + if vv == v { + return true + } + } + return false +} + +func stringSliceToInterfaceSlice(s []string) []interface{} { + res := make([]interface{}, len(s)) + for i, v := range s { + res[i] = v + } + return res +} diff --git a/casbin_test.go b/casbin_test.go new file mode 100644 index 0000000..2a99fec --- /dev/null +++ b/casbin_test.go @@ -0,0 +1,322 @@ +// Copyright 2023 CloudWeGo Authors +// +// 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 casbin + +import ( + "context" + "net/http" + "testing" + + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/app/server" + "github.com/cloudwego/hertz/pkg/common/test/assert" + "github.com/cloudwego/hertz/pkg/common/ut" +) + +const ( + modelFile = "./example/config/model.conf" + simplePolicy = "./example/config/policy.csv" + readWritePolicy = "./example/config/policy_read_write.csv" + userAdminPolicy = "./example/config/policy_user_admin.csv" +) + +var ( + LookupAlice = func(ctx context.Context, c *app.RequestContext) string { return "alice" } + LookupNil = func(ctx context.Context, c *app.RequestContext) string { return "" } +) + +func TestNewAuthMiddleware(t *testing.T) { + table := []struct { + lookup LookupHandler + expectedErr error + }{ + { + lookup: nil, + expectedErr: errLookupNil, + }, + { + lookup: LookupAlice, + expectedErr: nil, + }, + } + + for _, entry := range table { + _, err := NewCasbinMiddleware(modelFile, simplePolicy, entry.lookup) + assert.DeepEqual(t, entry.expectedErr, err) + } +} + +func TestRequiresPermissions(t *testing.T) { + tests := []struct { + policyFile string + lookup LookupHandler + permissions []string + logic Logic + expectedCode int + }{ + { + policyFile: simplePolicy, + lookup: LookupAlice, + permissions: []string{"book:read"}, + logic: AND, + expectedCode: http.StatusOK, + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + permissions: []string{"book:read"}, + logic: OR, + expectedCode: http.StatusOK, + }, + { + policyFile: readWritePolicy, + lookup: LookupAlice, + permissions: []string{"book:read", "book:write"}, + logic: AND, + expectedCode: http.StatusOK, + }, + { + policyFile: readWritePolicy, + lookup: LookupAlice, + permissions: []string{"book:read", "book:write"}, + logic: OR, + expectedCode: http.StatusOK, + }, + { + policyFile: simplePolicy, + lookup: LookupNil, + permissions: []string{"book:read"}, + logic: AND, + expectedCode: http.StatusUnauthorized, + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + permissions: []string{"book:write"}, + logic: AND, + expectedCode: http.StatusForbidden, + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + permissions: []string{"book:write"}, + logic: OR, + expectedCode: http.StatusForbidden, + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + permissions: []string{"book:read", "book:write"}, + logic: AND, + expectedCode: http.StatusForbidden, + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + permissions: []string{"book:review", "book:write"}, + logic: OR, + expectedCode: http.StatusForbidden, + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + permissions: []string{"readbook"}, + logic: AND, + expectedCode: http.StatusInternalServerError, + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + permissions: []string{":"}, + logic: AND, + expectedCode: http.StatusInternalServerError, + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + permissions: []string{"readbook"}, + logic: OR, + expectedCode: http.StatusInternalServerError, + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + permissions: []string{":"}, + logic: OR, + expectedCode: http.StatusInternalServerError, + }, + } + + for _, tt := range tests { + middleware, err := NewCasbinMiddleware(modelFile, tt.policyFile, tt.lookup) + if err != nil { + t.Fatal(err) + } + + r := setupRouter(middleware.RequiresPermissions(tt.permissions, WithLogic(tt.logic))) + + rsp := ut.PerformRequest(r.Engine, "GET", "/book", nil) + + assert.DeepEqual(t, tt.expectedCode, rsp.Code) + } +} + +func TestRequiresRoles(t *testing.T) { + tests := []struct { + policyFile string + lookup LookupHandler + roles []string + logic Logic + expectedCode int + }{ + { + policyFile: simplePolicy, + lookup: LookupAlice, + roles: []string{"user"}, + logic: AND, + expectedCode: http.StatusOK, + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + roles: []string{"user"}, + logic: OR, + expectedCode: http.StatusOK, + }, + { + policyFile: userAdminPolicy, + lookup: LookupAlice, + roles: []string{"user", "admin"}, + logic: AND, + expectedCode: http.StatusOK, + }, + { + policyFile: userAdminPolicy, + lookup: LookupAlice, + roles: []string{"user", "admin"}, + logic: OR, + expectedCode: http.StatusOK, + }, + { + policyFile: simplePolicy, + lookup: LookupNil, + roles: []string{"user"}, + logic: AND, + expectedCode: http.StatusUnauthorized, + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + roles: []string{"admin"}, + logic: AND, + expectedCode: http.StatusForbidden, + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + roles: []string{"admin"}, + logic: OR, + expectedCode: http.StatusForbidden, + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + roles: []string{"user", "admin"}, + logic: AND, + expectedCode: http.StatusForbidden, + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + roles: []string{"root", "admin"}, + logic: OR, + expectedCode: http.StatusForbidden, + }, + } + + for _, tt := range tests { + middleware, err := NewCasbinMiddleware(modelFile, tt.policyFile, tt.lookup) + if err != nil { + t.Fatal(err) + } + + r := setupRouter(middleware.RequiresRoles(tt.roles, WithLogic(tt.logic))) + + rsp := ut.PerformRequest(r.Engine, "GET", "/book", nil) + + assert.DeepEqual(t, tt.expectedCode, rsp.Code) + } +} + +func TestOption(t *testing.T) { + tests := []struct { + policyFile string + lookup LookupHandler + roles []string + logic Logic + expectedCode int + expectedTestHeader string + }{ + { + policyFile: simplePolicy, + lookup: LookupNil, + roles: []string{"user"}, + logic: AND, + expectedCode: http.StatusUnauthorized, + expectedTestHeader: "StatusUnauthorized", + }, + { + policyFile: simplePolicy, + lookup: LookupAlice, + roles: []string{"admin"}, + logic: AND, + expectedCode: http.StatusForbidden, + expectedTestHeader: "StatusForbidden", + }, + } + + for _, tt := range tests { + middleware, err := NewCasbinMiddleware(modelFile, tt.policyFile, tt.lookup) + if err != nil { + t.Fatal(err) + } + + r := setupRouter(middleware.RequiresRoles(tt.roles, WithLogic(tt.logic), + WithForbidden(func(c context.Context, ctx *app.RequestContext) { + ctx.Header("test", "StatusForbidden") + ctx.AbortWithStatus(http.StatusForbidden) + }), + WithUnauthorized(func(c context.Context, ctx *app.RequestContext) { + ctx.Header("test", "StatusUnauthorized") + ctx.AbortWithStatus(http.StatusUnauthorized) + }), + )) + + rsp := ut.PerformRequest(r.Engine, "GET", "/book", nil) + + assert.DeepEqual(t, tt.expectedCode, rsp.Code) + assert.DeepEqual(t, tt.expectedTestHeader, rsp.Header().Get("test")) + } +} + +func setupRouter(casbinMiddleware app.HandlerFunc) *server.Hertz { + r := server.Default() + + r.GET("/book", casbinMiddleware, func(ctx context.Context, c *app.RequestContext) { + c.String(http.StatusOK, "success") + }) + + return r +} diff --git a/example/config/model.conf b/example/config/model.conf new file mode 100644 index 0000000..71159e3 --- /dev/null +++ b/example/config/model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[role_definition] +g = _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act \ No newline at end of file diff --git a/example/config/policy.csv b/example/config/policy.csv new file mode 100644 index 0000000..d6ab2b4 --- /dev/null +++ b/example/config/policy.csv @@ -0,0 +1,2 @@ +p,user,book,read +g,alice,user \ No newline at end of file diff --git a/example/config/policy_read_write.csv b/example/config/policy_read_write.csv new file mode 100644 index 0000000..950d8c8 --- /dev/null +++ b/example/config/policy_read_write.csv @@ -0,0 +1,3 @@ +p,user,book,read +p,user,book,write +g,alice,user \ No newline at end of file diff --git a/example/config/policy_user_admin.csv b/example/config/policy_user_admin.csv new file mode 100644 index 0000000..01b21ad --- /dev/null +++ b/example/config/policy_user_admin.csv @@ -0,0 +1,4 @@ +p,user,book,read +p,admin,book,read +g,alice,user +g,alice,admin \ No newline at end of file diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..7c54df1 --- /dev/null +++ b/example/main.go @@ -0,0 +1,72 @@ +// Copyright 2023 CloudWeGo Authors +// +// 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 main + +import ( + "context" + "log" + + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/app/server" + "github.com/hertz-contrib/casbin" + "github.com/hertz-contrib/sessions" + "github.com/hertz-contrib/sessions/cookie" +) + +func main() { + h := server.Default() + + // Using sessions and casbin. + store := cookie.NewStore([]byte("secret")) + h.Use(sessions.New("session", store)) + auth, err := casbin.NewCasbinMiddleware("example/config/model.conf", "example/config/policy.csv", subjectFromSession) + if err != nil { + log.Fatal(err) + } + + h.POST("/login", func(ctx context.Context, c *app.RequestContext) { + // Verify username and password. + // ... + + // Store current subject in session + session := sessions.Default(c) + session.Set("name", "alice") + err := session.Save() + if err != nil { + log.Fatal(err) + } + c.String(200, "you login successfully") + }) + + h.GET("/book", auth.RequiresPermissions([]string{"book:read"}, casbin.WithLogic(casbin.AND)), func(ctx context.Context, c *app.RequestContext) { + c.String(200, "you read the book successfully") + }) + h.POST("/book", auth.RequiresRoles([]string{"user"}, casbin.WithLogic(casbin.AND)), func(ctx context.Context, c *app.RequestContext) { + c.String(200, "you posted a book successfully") + }) + + h.Spin() +} + +// subjectFromSession get subject from session. +func subjectFromSession(ctx context.Context, c *app.RequestContext) string { + // Get subject from session. + session := sessions.Default(c) + if subject, ok := session.Get("name").(string); !ok { + return "" + } else { + return subject + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..cd5f9ae --- /dev/null +++ b/go.mod @@ -0,0 +1,36 @@ +module github.com/hertz-contrib/casbin + +go 1.18 + +require ( + github.com/casbin/casbin/v2 v2.61.0 + github.com/cloudwego/hertz v0.5.2 + github.com/hertz-contrib/sessions v1.0.1 +) + +require ( + github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect + github.com/bytedance/go-tagexpr/v2 v2.9.2 // indirect + github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7 // indirect + github.com/bytedance/sonic v1.5.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06 // indirect + github.com/cloudwego/netpoll v0.3.1 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/golang/protobuf v1.5.0 // indirect + github.com/gorilla/context v1.1.1 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect + github.com/gorilla/sessions v1.2.1 // indirect + github.com/henrylee2cn/ameda v1.4.10 // indirect + github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/nyaruka/phonenumbers v1.0.55 // indirect + github.com/stretchr/testify v1.7.1 // indirect + github.com/tidwall/gjson v1.14.3 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c5c8077 --- /dev/null +++ b/go.sum @@ -0,0 +1,101 @@ +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= +github.com/bytedance/go-tagexpr/v2 v2.9.2 h1:QySJaAIQgOEDQBLS3x9BxOWrnhqu5sQ+f6HaZIxD39I= +github.com/bytedance/go-tagexpr/v2 v2.9.2/go.mod h1:5qsx05dYOiUXOUgnQ7w3Oz8BYs2qtM/bJokdLb79wRM= +github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7 h1:PtwsQyQJGxf8iaPptPNaduEIu9BnrNms+pcRdHAxZaM= +github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= +github.com/bytedance/sonic v1.3.0/go.mod h1:V973WhNhGmvHxW6nQmsHEfHaoU9F3zTF+93rH03hcUQ= +github.com/bytedance/sonic v1.5.0 h1:XWdTi8bwPgxIML+eNV1IwNuTROK6EUrQ65ey8yd6fRQ= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/casbin/casbin/v2 v2.61.0 h1:4tUV92uBw1nDoMbQJh/e/S4HiFt4F6Rr4+kvuXcCJ6U= +github.com/casbin/casbin/v2 v2.61.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06 h1:1sDoSuDPWzhkdzNVxCxtIaKiAe96ESVPv8coGwc1gZ4= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/cloudwego/hertz v0.3.0/go.mod h1:GWWYlAVkq1gDu6vJd/XNciWsP6q0d4TrEKk5fpJYF04= +github.com/cloudwego/hertz v0.5.2 h1:SOxmJo1KXjjWQjJ7OwxCvEePiR92PScdW5JM1p1HpHo= +github.com/cloudwego/hertz v0.5.2/go.mod h1:K1U0RlU07CDeBINfHNbafH/3j9uSgIW8otbjUys3OPY= +github.com/cloudwego/netpoll v0.2.4/go.mod h1:1T2WVuQ+MQw6h6DpE45MohSvDTKdy2DlzCx2KsnPI4E= +github.com/cloudwego/netpoll v0.3.1 h1:xByoORmCLIyKZ8gS+da06WDo3j+jvmhaqS2KeKejtBk= +github.com/cloudwego/netpoll v0.3.1/go.mod h1:1T2WVuQ+MQw6h6DpE45MohSvDTKdy2DlzCx2KsnPI4E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/henrylee2cn/ameda v1.4.8/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4= +github.com/henrylee2cn/ameda v1.4.10 h1:JdvI2Ekq7tapdPsuhrc4CaFiqw6QXFvZIULWJgQyCAk= +github.com/henrylee2cn/ameda v1.4.10/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4= +github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8 h1:yE9ULgp02BhYIrO6sdV/FPe0xQM6fNHkVQW2IAymfM0= +github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8/go.mod h1:Nhe/DM3671a5udlv2AdV2ni/MZzgfv2qrPL5nIi3EGQ= +github.com/hertz-contrib/sessions v1.0.1 h1:2puqvNqzyuPC3+5gzMRS+xhvGdNq3Ux7OVEewLSJbAk= +github.com/hertz-contrib/sessions v1.0.1/go.mod h1:gyVq7zq5emvSTLpp4VrlLOXjnajorYZ1hH4ZSX9Afb0= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuwcCl3yg= +github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= +github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/licenses/LICENSE-casbin.txt b/licenses/LICENSE-casbin.txt new file mode 100644 index 0000000..9c8f3ea --- /dev/null +++ b/licenses/LICENSE-casbin.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. \ No newline at end of file diff --git a/licenses/LICENSE-fiber-casbin.txt b/licenses/LICENSE-fiber-casbin.txt new file mode 100644 index 0000000..df3416d --- /dev/null +++ b/licenses/LICENSE-fiber-casbin.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Fiber + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/licenses/LICENSE-gin-casbin.txt b/licenses/LICENSE-gin-casbin.txt new file mode 100644 index 0000000..985157f --- /dev/null +++ b/licenses/LICENSE-gin-casbin.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 maxwellhertz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/option.go b/option.go new file mode 100644 index 0000000..3f00527 --- /dev/null +++ b/option.go @@ -0,0 +1,140 @@ +// Copyright 2023 CloudWeGo Authors +// +// 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 casbin + +import ( + "context" + "errors" + "net/http" + "strings" + + "github.com/cloudwego/hertz/pkg/app" +) + +// LookupHandler is used to look up current subject in runtime. +// If it can not find anything, just return an empty string. +type LookupHandler func(ctx context.Context, c *app.RequestContext) string + +// Logic is the logical operation (AND/OR) used in permission checks +// in case multiple permissions or roles are specified. +type Logic int + +// PermissionParserFunc is used for parsing the permission +// to extract object and action usually +type PermissionParserFunc func(str string) []string + +const ( + AND Logic = iota + OR +) + +var errLookupNil = errors.New("[Casbin] Lookup is nil") + +// Option is the only struct that can be used to set Options. +type Option struct { + F func(o *Options) +} + +type Options struct { + // Logic is the logical operation (AND/OR) used in permission checks + // in case multiple permissions or roles are specified. + // Optional. Default: AND + Logic Logic + + // PermissionParserFunc is used for parsing the permission + // to extract object and action usually + // Optional. Default: PermissionParserWithSeparator(":") + PermissionParser PermissionParserFunc + + // Unauthorized defines the response body for unauthorized responses. + // Optional. Default: func(ctx context.Context, c *app.RequestContext) { + // c.AbortWithStatus(http.StatusUnauthorized) + // }, + Unauthorized app.HandlerFunc + + // Forbidden defines the response body for forbidden responses. + // Optional. Default: func(ctx context.Context, c *app.RequestContext) { + // c.AbortWithStatus(http.StatusForbidden) + // }, + Forbidden app.HandlerFunc +} + +// Apply to apply options. +func (o *Options) Apply(opts []Option) { + for _, op := range opts { + op.F(o) + } +} + +var OptionsDefault = Options{ + Logic: AND, + PermissionParser: PermissionParserWithSeparator(":"), + Unauthorized: func(ctx context.Context, c *app.RequestContext) { + c.AbortWithStatus(http.StatusUnauthorized) + }, + Forbidden: func(ctx context.Context, c *app.RequestContext) { + c.AbortWithStatus(http.StatusForbidden) + }, +} + +func NewOptions(opts ...Option) *Options { + options := &Options{ + Logic: OptionsDefault.Logic, + PermissionParser: OptionsDefault.PermissionParser, + Unauthorized: OptionsDefault.Unauthorized, + Forbidden: OptionsDefault.Forbidden, + } + options.Apply(opts) + return options +} + +// WithLogic sets the logical operator used in permission or role checks. +func WithLogic(logic Logic) Option { + return Option{ + F: func(o *Options) { + o.Logic = logic + }, + } +} + +func WithPermissionParser(pp PermissionParserFunc) Option { + return Option{ + F: func(o *Options) { + o.PermissionParser = pp + }, + } +} + +func WithUnauthorized(u app.HandlerFunc) Option { + return Option{ + F: func(o *Options) { + o.Unauthorized = u + }, + } +} + +func WithForbidden(f app.HandlerFunc) Option { + return Option{ + F: func(o *Options) { + o.Forbidden = f + }, + } +} + +func PermissionParserWithSeparator(sep string) PermissionParserFunc { + return func(str string) []string { + return strings.Split(str, sep) + } +} diff --git a/profile/README.md b/profile/README.md deleted file mode 100644 index 2127160..0000000 --- a/profile/README.md +++ /dev/null @@ -1,13 +0,0 @@ -## Hi there 👋 - -🙋‍♀️ A short introduction - CloudWeGo is an open-source middleware set launched by ByteDance that can be used to quickly build enterprise-class cloud native architectures. The common characteristics of CloudWeGo projects are high performance, high scalability, high reliability and focusing on microservices communication and governance. - -🌈 Community Membership - the [Responsibilities and Requirements](https://github.com/cloudwego/community/blob/main/COMMUNITY_MEMBERSHIP.md) of contributor roles in CloudWeGo. - -👩‍💻 Useful resources - [Portal](https://www.cloudwego.io/), [Community](https://www.cloudwego.io/zh/community/), [Blogs](https://www.cloudwego.io/zh/blog/), [Use Cases](https://www.cloudwego.io/zh/cooperation/) - -🍿 Security - [Vulnerability Reporting](https://www.cloudwego.io/zh/security/vulnerability-reporting/), [Safety Bulletin](https://www.cloudwego.io/zh/security/safety-bulletin/) - -🌲 Ecosystem - [Kitex-contrib](https://github.com/kitex-contrib), [Hertz-contrib](https://github.com/hertz-contrib), [Volo-rs](https://github.com/volo-rs) - -🎊 Example - [kitex-example](https://github.com/cloudwego/kitex-examples), [hertz-example](https://github.com/cloudwego/hertz-examples), [biz-demo](https://github.com/cloudwego/biz-demo), [netpoll-example](https://github.com/cloudwego/netpoll-examples) From 13a96a7261f0e0c8ae8d24046c601de76bd1fd2f Mon Sep 17 00:00:00 2001 From: L2ncE Date: Sun, 5 Feb 2023 20:47:57 +0800 Subject: [PATCH 2/3] fix: lint --- casbin.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/casbin.go b/casbin.go index 2a39b5a..f1101af 100644 --- a/casbin.go +++ b/casbin.go @@ -113,7 +113,6 @@ func (m *Middleware) RequiresPermissions(permissions []string, opts ...Option) a return } c.Next(ctx) - return } } @@ -161,7 +160,6 @@ func (m *Middleware) RequiresRoles(requiredRoles []string, opts ...Option) app.H return } c.Next(ctx) - return } } From 2da109e50d083ff1dd49faa5a1cafcfe57294ddc Mon Sep 17 00:00:00 2001 From: L2ncE Date: Mon, 6 Feb 2023 13:04:21 +0800 Subject: [PATCH 3/3] optimize: use const and improve option test --- README.md | 12 ++++----- casbin.go | 12 ++++----- casbin_test.go | 67 ++++++++++++++++++++++++++++---------------------- option.go | 14 +++++++---- 4 files changed, 58 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index f4e9251..4f0cc93 100644 --- a/README.md +++ b/README.md @@ -81,10 +81,10 @@ func subjectFromSession(ctx context.Context, c *app.RequestContext) string { ## Options -| Option | Default | Description | -| ---------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -| Logic | `AND` | Logic is the logical operation (AND/OR) used in permission checks in case multiple permissions or roles are specified. | -| PermissionParser | `PermissionParserWithSeparator(":")` | PermissionParserFunc is used for parsing the permission to extract object and action usually. | -| Unauthorized | `func(ctx context.Context, c *app.RequestContext) { c.AbortWithStatus(http.StatusUnauthorized) }` | Unauthorized defines the response body for unauthorized responses. | -| Forbidden | `func(ctx context.Context, c *app.RequestContext) { c.AbortWithStatus(http.StatusForbidden) }` | Forbidden defines the response body for forbidden responses. | +| Option | Default | Description | +|------------------|--------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------| +| Logic | `AND` | Logic is the logical operation (AND/OR) used in permission checks in case multiple permissions or roles are specified. | +| PermissionParser | `PermissionParserWithSeparator(":")` | PermissionParserFunc is used for parsing the permission to extract object and action usually. | +| Unauthorized | `func(ctx context.Context, c *app.RequestContext) { c.AbortWithStatus(consts.StatusUnauthorized) }` | Unauthorized defines the response body for unauthorized responses. | +| Forbidden | `func(ctx context.Context, c *app.RequestContext) { c.AbortWithStatus(consts.StatusForbidden) }` | Forbidden defines the response body for forbidden responses. | diff --git a/casbin.go b/casbin.go index f1101af..6ffde29 100644 --- a/casbin.go +++ b/casbin.go @@ -16,10 +16,10 @@ package casbin import ( "context" - "net/http" "github.com/casbin/casbin/v2" "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/protocol/consts" ) type Middleware struct { @@ -79,11 +79,11 @@ func (m *Middleware) RequiresPermissions(permissions []string, opts ...Option) a vals := append([]string{sub}, options.PermissionParser(permission)...) if vals[0] == "" || vals[1] == "" { // Can not handle any illegal permission strings. - c.AbortWithStatus(http.StatusInternalServerError) + c.AbortWithStatus(consts.StatusInternalServerError) return } if ok, err := m.enforcer.Enforce(stringSliceToInterfaceSlice(vals)...); err != nil { - c.AbortWithStatus(http.StatusInternalServerError) + c.AbortWithStatus(consts.StatusInternalServerError) return } else if !ok { options.Forbidden(ctx, c) @@ -98,11 +98,11 @@ func (m *Middleware) RequiresPermissions(permissions []string, opts ...Option) a values := append([]string{sub}, options.PermissionParser(permission)...) if values[0] == "" || values[1] == "" { // Can not handle any illegal permission strings. - c.AbortWithStatus(http.StatusInternalServerError) + c.AbortWithStatus(consts.StatusInternalServerError) return } if ok, err := m.enforcer.Enforce(stringSliceToInterfaceSlice(values)...); err != nil { - c.AbortWithStatus(http.StatusInternalServerError) + c.AbortWithStatus(consts.StatusInternalServerError) return } else if ok { c.Next(ctx) @@ -134,7 +134,7 @@ func (m *Middleware) RequiresRoles(requiredRoles []string, opts ...Option) app.H } actualRoles, err := m.enforcer.GetRolesForUser(sub) if err != nil { - c.AbortWithStatus(http.StatusInternalServerError) + c.AbortWithStatus(consts.StatusInternalServerError) return } diff --git a/casbin_test.go b/casbin_test.go index 2a99fec..f449401 100644 --- a/casbin_test.go +++ b/casbin_test.go @@ -23,6 +23,7 @@ import ( "github.com/cloudwego/hertz/pkg/app/server" "github.com/cloudwego/hertz/pkg/common/test/assert" "github.com/cloudwego/hertz/pkg/common/ut" + "github.com/cloudwego/hertz/pkg/protocol/consts" ) const ( @@ -71,91 +72,91 @@ func TestRequiresPermissions(t *testing.T) { lookup: LookupAlice, permissions: []string{"book:read"}, logic: AND, - expectedCode: http.StatusOK, + expectedCode: consts.StatusOK, }, { policyFile: simplePolicy, lookup: LookupAlice, permissions: []string{"book:read"}, logic: OR, - expectedCode: http.StatusOK, + expectedCode: consts.StatusOK, }, { policyFile: readWritePolicy, lookup: LookupAlice, permissions: []string{"book:read", "book:write"}, logic: AND, - expectedCode: http.StatusOK, + expectedCode: consts.StatusOK, }, { policyFile: readWritePolicy, lookup: LookupAlice, permissions: []string{"book:read", "book:write"}, logic: OR, - expectedCode: http.StatusOK, + expectedCode: consts.StatusOK, }, { policyFile: simplePolicy, lookup: LookupNil, permissions: []string{"book:read"}, logic: AND, - expectedCode: http.StatusUnauthorized, + expectedCode: consts.StatusUnauthorized, }, { policyFile: simplePolicy, lookup: LookupAlice, permissions: []string{"book:write"}, logic: AND, - expectedCode: http.StatusForbidden, + expectedCode: consts.StatusForbidden, }, { policyFile: simplePolicy, lookup: LookupAlice, permissions: []string{"book:write"}, logic: OR, - expectedCode: http.StatusForbidden, + expectedCode: consts.StatusForbidden, }, { policyFile: simplePolicy, lookup: LookupAlice, permissions: []string{"book:read", "book:write"}, logic: AND, - expectedCode: http.StatusForbidden, + expectedCode: consts.StatusForbidden, }, { policyFile: simplePolicy, lookup: LookupAlice, permissions: []string{"book:review", "book:write"}, logic: OR, - expectedCode: http.StatusForbidden, + expectedCode: consts.StatusForbidden, }, { policyFile: simplePolicy, lookup: LookupAlice, permissions: []string{"readbook"}, logic: AND, - expectedCode: http.StatusInternalServerError, + expectedCode: consts.StatusInternalServerError, }, { policyFile: simplePolicy, lookup: LookupAlice, permissions: []string{":"}, logic: AND, - expectedCode: http.StatusInternalServerError, + expectedCode: consts.StatusInternalServerError, }, { policyFile: simplePolicy, lookup: LookupAlice, permissions: []string{"readbook"}, logic: OR, - expectedCode: http.StatusInternalServerError, + expectedCode: consts.StatusInternalServerError, }, { policyFile: simplePolicy, lookup: LookupAlice, permissions: []string{":"}, logic: OR, - expectedCode: http.StatusInternalServerError, + expectedCode: consts.StatusInternalServerError, }, } @@ -186,63 +187,63 @@ func TestRequiresRoles(t *testing.T) { lookup: LookupAlice, roles: []string{"user"}, logic: AND, - expectedCode: http.StatusOK, + expectedCode: consts.StatusOK, }, { policyFile: simplePolicy, lookup: LookupAlice, roles: []string{"user"}, logic: OR, - expectedCode: http.StatusOK, + expectedCode: consts.StatusOK, }, { policyFile: userAdminPolicy, lookup: LookupAlice, roles: []string{"user", "admin"}, logic: AND, - expectedCode: http.StatusOK, + expectedCode: consts.StatusOK, }, { policyFile: userAdminPolicy, lookup: LookupAlice, roles: []string{"user", "admin"}, logic: OR, - expectedCode: http.StatusOK, + expectedCode: consts.StatusOK, }, { policyFile: simplePolicy, lookup: LookupNil, roles: []string{"user"}, logic: AND, - expectedCode: http.StatusUnauthorized, + expectedCode: consts.StatusUnauthorized, }, { policyFile: simplePolicy, lookup: LookupAlice, roles: []string{"admin"}, logic: AND, - expectedCode: http.StatusForbidden, + expectedCode: consts.StatusForbidden, }, { policyFile: simplePolicy, lookup: LookupAlice, roles: []string{"admin"}, logic: OR, - expectedCode: http.StatusForbidden, + expectedCode: consts.StatusForbidden, }, { policyFile: simplePolicy, lookup: LookupAlice, roles: []string{"user", "admin"}, logic: AND, - expectedCode: http.StatusForbidden, + expectedCode: consts.StatusForbidden, }, { policyFile: simplePolicy, lookup: LookupAlice, roles: []string{"root", "admin"}, logic: OR, - expectedCode: http.StatusForbidden, + expectedCode: consts.StatusForbidden, }, } @@ -261,10 +262,15 @@ func TestRequiresRoles(t *testing.T) { } func TestOption(t *testing.T) { + // Test default logic + opts := NewOptions() + assert.DeepEqual(t, opts.Logic, AND) + + // Test WithFunc tests := []struct { policyFile string lookup LookupHandler - roles []string + permissions []string logic Logic expectedCode int expectedTestHeader string @@ -272,17 +278,17 @@ func TestOption(t *testing.T) { { policyFile: simplePolicy, lookup: LookupNil, - roles: []string{"user"}, + permissions: []string{"book-read"}, logic: AND, - expectedCode: http.StatusUnauthorized, + expectedCode: consts.StatusUnauthorized, expectedTestHeader: "StatusUnauthorized", }, { policyFile: simplePolicy, lookup: LookupAlice, - roles: []string{"admin"}, + permissions: []string{"book-write"}, logic: AND, - expectedCode: http.StatusForbidden, + expectedCode: consts.StatusForbidden, expectedTestHeader: "StatusForbidden", }, } @@ -293,15 +299,16 @@ func TestOption(t *testing.T) { t.Fatal(err) } - r := setupRouter(middleware.RequiresRoles(tt.roles, WithLogic(tt.logic), + r := setupRouter(middleware.RequiresPermissions(tt.permissions, WithLogic(tt.logic), WithForbidden(func(c context.Context, ctx *app.RequestContext) { ctx.Header("test", "StatusForbidden") - ctx.AbortWithStatus(http.StatusForbidden) + ctx.AbortWithStatus(consts.StatusForbidden) }), WithUnauthorized(func(c context.Context, ctx *app.RequestContext) { ctx.Header("test", "StatusUnauthorized") - ctx.AbortWithStatus(http.StatusUnauthorized) + ctx.AbortWithStatus(consts.StatusUnauthorized) }), + WithPermissionParser(PermissionParserWithSeparator("-")), )) rsp := ut.PerformRequest(r.Engine, "GET", "/book", nil) diff --git a/option.go b/option.go index 3f00527..2d2da45 100644 --- a/option.go +++ b/option.go @@ -17,10 +17,10 @@ package casbin import ( "context" "errors" - "net/http" "strings" "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/protocol/consts" ) // LookupHandler is used to look up current subject in runtime. @@ -60,13 +60,13 @@ type Options struct { // Unauthorized defines the response body for unauthorized responses. // Optional. Default: func(ctx context.Context, c *app.RequestContext) { - // c.AbortWithStatus(http.StatusUnauthorized) + // c.AbortWithStatus(consts.StatusUnauthorized) // }, Unauthorized app.HandlerFunc // Forbidden defines the response body for forbidden responses. // Optional. Default: func(ctx context.Context, c *app.RequestContext) { - // c.AbortWithStatus(http.StatusForbidden) + // c.AbortWithStatus(consts.StatusForbidden) // }, Forbidden app.HandlerFunc } @@ -82,10 +82,10 @@ var OptionsDefault = Options{ Logic: AND, PermissionParser: PermissionParserWithSeparator(":"), Unauthorized: func(ctx context.Context, c *app.RequestContext) { - c.AbortWithStatus(http.StatusUnauthorized) + c.AbortWithStatus(consts.StatusUnauthorized) }, Forbidden: func(ctx context.Context, c *app.RequestContext) { - c.AbortWithStatus(http.StatusForbidden) + c.AbortWithStatus(consts.StatusForbidden) }, } @@ -109,6 +109,7 @@ func WithLogic(logic Logic) Option { } } +// WithPermissionParser sets parsing the permission func. func WithPermissionParser(pp PermissionParserFunc) Option { return Option{ F: func(o *Options) { @@ -117,6 +118,7 @@ func WithPermissionParser(pp PermissionParserFunc) Option { } } +// WithUnauthorized defines the response body for unauthorized responses. func WithUnauthorized(u app.HandlerFunc) Option { return Option{ F: func(o *Options) { @@ -125,6 +127,7 @@ func WithUnauthorized(u app.HandlerFunc) Option { } } +// WithForbidden defines the response body for forbidden responses. func WithForbidden(f app.HandlerFunc) Option { return Option{ F: func(o *Options) { @@ -133,6 +136,7 @@ func WithForbidden(f app.HandlerFunc) Option { } } +// PermissionParserWithSeparator is a permission parser with separator. func PermissionParserWithSeparator(sep string) PermissionParserFunc { return func(str string) []string { return strings.Split(str, sep)