This is an automated email from the ASF dual-hosted git repository.

littlecui pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/servicecomb-kie.git


The following commit(s) were added to refs/heads/dev by this push:
     new c589029  Feature:新增rbac认证 (#262)
c589029 is described below

commit c589029525e1588514f6e787f0b9db6fd605673c
Author: little-cui <[email protected]>
AuthorDate: Thu Nov 10 01:05:04 2022 +0800

    Feature:新增rbac认证 (#262)
    
    * 增加RBAC认证
    
    * 增加RBAC认证
    
    Co-authored-by: 123yangxiong <[email protected]>
---
 go.sum                                 |   5 --
 pkg/util/concurrent_map_go19.go        |  98 +++++++++++++++++++++
 pkg/util/context.go                    | 106 ++++++++++++++++++++++
 server/datasource/auth/account_role.go | 106 ++++++++++++++++++++++
 server/datasource/auth/checkKVPerm.go  |  75 ++++++++++++++++
 server/datasource/auth/decision.go     | 155 +++++++++++++++++++++++++++++++++
 server/datasource/auth/filter_kvdoc.go |  53 +++++++++++
 server/datasource/auth/makeScope.go    |  28 ++++++
 server/datasource/auth/perm.go         |  36 ++++++++
 server/datasource/auth/rbac.go         |  36 ++++++++
 server/datasource/etcd/kv/kv_dao.go    |  43 ++++++++-
 server/datasource/etcd/rbac/role.go    |  60 +++++++++++++
 server/datasource/mongo/rbac/role.go   |  60 +++++++++++++
 server/datasource/rbac/rbac.go         |  13 +++
 server/rbac/rbac.go                    |   2 +
 server/resource/v1/kv_resource.go      |   1 +
 16 files changed, 871 insertions(+), 6 deletions(-)

diff --git a/go.sum b/go.sum
index a24292e..170a95d 100644
--- a/go.sum
+++ b/go.sum
@@ -117,7 +117,6 @@ github.com/dustin/go-humanize v1.0.0/go.mod 
h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn
 github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod 
h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
 github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod 
h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod 
h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
-github.com/emicklei/go-restful v2.12.0+incompatible 
h1:SIvoTSbsMEwuM3dzFirLwKc4BH6VXP5CNf+G1FfJVr4=
 github.com/emicklei/go-restful v2.12.0+incompatible/go.mod 
h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
 github.com/emicklei/go-restful 
v2.15.1-0.20220703112237-d9c71e118c95+incompatible 
h1:o87Jf90Yb9ZvrdJKy2jYAdzosa7MtC2H214tJxJx7As=
 github.com/emicklei/go-restful 
v2.15.1-0.20220703112237-d9c71e118c95+incompatible/go.mod 
h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
@@ -160,11 +159,8 @@ github.com/go-chassis/go-archaius v1.5.1/go.mod 
h1:QPwvvtBxvwiC48rmydoAqxopqOr93
 github.com/go-chassis/go-archaius v1.5.2-0.20210301074935-e4694f6b077b 
h1:0u2kNkdw+J8OublV27I1Xn6berg3MiUm5GSl/T3qXSg=
 github.com/go-chassis/go-archaius v1.5.2-0.20210301074935-e4694f6b077b/go.mod 
h1:qjfG7opNF/QTzj7SyVIn/eIawaPhl7xeGgg8kxzFsDw=
 github.com/go-chassis/go-chassis/v2 v2.3.0/go.mod 
h1:iyJ2DWSkqfnCmad/0Il9nXWHaob7RcwPGlIDRNxccH0=
-github.com/go-chassis/go-chassis/v2 v2.3.1-0.20211217084436-360a6a6a0ef3 
h1:Ctxgyo/gc5sogcCJLoIvIKj9kEUzJOlbENpYO/nSPWA=
-github.com/go-chassis/go-chassis/v2 
v2.3.1-0.20211217084436-360a6a6a0ef3/go.mod 
h1:oMnRaz2P+OPTtEfh2HEuiF9YzdYHQrNVPXdnbKzKO9w=
 github.com/go-chassis/go-chassis/v2 v2.5.2 
h1:Au+rNk3ZYoYHp73HHWMVrI5FIIOBVlt12SyXZjDnC2Q=
 github.com/go-chassis/go-chassis/v2 v2.5.2/go.mod 
h1:Ru3kB+ndcxxFZl3clnuihKVv+gOi7lOVR9Ghhtw8jaQ=
-github.com/go-chassis/go-restful-swagger20 v1.0.3 
h1:kWfeLwMwJZVkXP1zNyFpkmR41UZ55UTcOptTteXhvEs=
 github.com/go-chassis/go-restful-swagger20 v1.0.3/go.mod 
h1:eW62fYuzlNFDvIacB6AV8bhUDCTy4myfTCv0bT9Gbb0=
 github.com/go-chassis/go-restful-swagger20 
v1.0.4-0.20220704025524-9243cbee26b7 
h1:EOIGW+inOz52zh6vgr9EQHvvgL2w/VghAeCQIFOVUSE=
 github.com/go-chassis/go-restful-swagger20 
v1.0.4-0.20220704025524-9243cbee26b7/go.mod 
h1:pSGkT+ksxlMgytyJb4IAz8aZih6OLE1++d9CE6aO9Hg=
@@ -768,7 +764,6 @@ golang.org/x/text v0.3.0/go.mod 
h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod 
h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
diff --git a/pkg/util/concurrent_map_go19.go b/pkg/util/concurrent_map_go19.go
new file mode 100644
index 0000000..6dc0518
--- /dev/null
+++ b/pkg/util/concurrent_map_go19.go
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 util
+
+import "sync"
+
+type MapItem struct {
+       Key   interface{}
+       Value interface{}
+}
+
+type ConcurrentMap struct {
+       mapper    sync.Map
+       fetchLock sync.RWMutex
+}
+
+func (cm *ConcurrentMap) Put(key, val interface{}) {
+       cm.fetchLock.RLock()
+       cm.mapper.Store(key, val)
+       cm.fetchLock.RUnlock()
+}
+
+func (cm *ConcurrentMap) PutIfAbsent(key, val interface{}) (exist interface{}) 
{
+       cm.fetchLock.RLock()
+       exist, _ = cm.mapper.LoadOrStore(key, val)
+       cm.fetchLock.RUnlock()
+       return
+}
+
+func (cm *ConcurrentMap) Fetch(key interface{}, f func() (interface{}, error)) 
(v interface{}, err error) {
+       if exist, b := cm.mapper.Load(key); b {
+               return exist, nil
+       }
+
+       cm.fetchLock.Lock()
+       if exist, b := cm.mapper.Load(key); b {
+               cm.fetchLock.Unlock()
+               return exist, nil
+       }
+
+       if v, err = f(); err != nil {
+               cm.fetchLock.Unlock()
+               return nil, err
+       }
+
+       cm.mapper.Store(key, v)
+       cm.fetchLock.Unlock()
+       return
+}
+
+func (cm *ConcurrentMap) Get(key interface{}) (val interface{}, b bool) {
+       return cm.mapper.Load(key)
+}
+
+func (cm *ConcurrentMap) Remove(key interface{}) {
+       cm.fetchLock.RLock()
+       cm.mapper.Delete(key)
+       cm.fetchLock.RUnlock()
+}
+
+func (cm *ConcurrentMap) Clear() {
+       cm.fetchLock.RLock()
+       cm.mapper = sync.Map{}
+       cm.fetchLock.RUnlock()
+}
+
+func (cm *ConcurrentMap) Size() (s int) {
+       cm.mapper.Range(func(_, _ interface{}) bool {
+               s++
+               return true
+       })
+       return
+}
+
+func (cm *ConcurrentMap) ForEach(f func(item MapItem) (next bool)) {
+       cm.mapper.Range(func(key, value interface{}) bool {
+               return f(MapItem{key, value})
+       })
+}
+
+func NewConcurrentMap(_ int) *ConcurrentMap {
+       return new(ConcurrentMap)
+}
diff --git a/pkg/util/context.go b/pkg/util/context.go
new file mode 100644
index 0000000..3b6c168
--- /dev/null
+++ b/pkg/util/context.go
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 util
+
+import (
+       "context"
+       "google.golang.org/grpc/metadata"
+       "net/http"
+       "time"
+)
+
+type StringContext struct {
+       parentCtx context.Context
+       kv        *ConcurrentMap
+}
+
+type CtxKey string
+
+func (c *StringContext) Deadline() (deadline time.Time, ok bool) {
+       return c.parentCtx.Deadline()
+}
+
+func (c *StringContext) Done() <-chan struct{} {
+       return c.parentCtx.Done()
+}
+
+func (c *StringContext) Err() error {
+       return c.parentCtx.Err()
+}
+
+func (c *StringContext) Value(key interface{}) interface{} {
+       k, ok := key.(CtxKey)
+       if !ok {
+               return c.parentCtx.Value(key)
+       }
+       v, ok := c.kv.Get(k)
+       if !ok {
+               return FromContext(c.parentCtx, k)
+       }
+       return v
+}
+
+func (c *StringContext) SetKV(key CtxKey, val interface{}) {
+       c.kv.Put(key, val)
+}
+
+func NewStringContext(ctx context.Context) *StringContext {
+       strCtx, ok := ctx.(*StringContext)
+       if !ok {
+               strCtx = &StringContext{
+                       parentCtx: ctx,
+                       kv:        NewConcurrentMap(0),
+               }
+       }
+       return strCtx
+}
+
+func SetContext(ctx context.Context, key CtxKey, val interface{}) 
context.Context {
+       strCtx := NewStringContext(ctx)
+       strCtx.SetKV(key, val)
+       return strCtx
+}
+
+// FromContext return the value from ctx, return empty STRING if not found
+func FromContext(ctx context.Context, key CtxKey) interface{} {
+       if v := ctx.Value(key); v != nil {
+               return v
+       }
+       return FromMetadata(ctx, key)
+}
+
+func SetRequestContext(r *http.Request, key CtxKey, val interface{}) 
*http.Request {
+       ctx := r.Context()
+       ctx = SetContext(ctx, key, val)
+       if ctx != r.Context() {
+               nr := r.WithContext(ctx)
+               *r = *nr
+       }
+       return r
+}
+
+func FromMetadata(ctx context.Context, key CtxKey) string {
+       md, ok := metadata.FromIncomingContext(ctx)
+       if !ok {
+               return ""
+       }
+       if values, ok := md[string(key)]; ok && len(values) > 0 {
+               return values[0]
+       }
+       return ""
+}
diff --git a/server/datasource/auth/account_role.go 
b/server/datasource/auth/account_role.go
new file mode 100644
index 0000000..5235af2
--- /dev/null
+++ b/server/datasource/auth/account_role.go
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 auth
+
+import (
+       "context"
+       "errors"
+       "fmt"
+       "strings"
+
+       "github.com/apache/servicecomb-kie/pkg/util"
+       rbacmodel "github.com/go-chassis/cari/rbac"
+       "github.com/go-chassis/go-chassis/v2/security/authr"
+       "github.com/go-chassis/go-chassis/v2/server/restful"
+       "github.com/go-chassis/openlog"
+)
+
+const (
+       RootName = "root"
+)
+
+var ErrNoRoles = errors.New("no role found in token")
+
+func GetAccountFromReq(ctx context.Context) (*rbacmodel.Account, error) {
+       v, ok := util.FromContext(ctx, restful.HeaderAuth).(string)
+       if !ok || v == "" {
+               return nil, rbacmodel.NewError(rbacmodel.ErrNoAuthHeader, "")
+       }
+
+       accountExist(ctx, v)
+
+       s := strings.Split(v, " ")
+       if len(s) != 2 {
+               return nil, rbacmodel.ErrInvalidHeader
+       }
+       to := s[1]
+
+       claims, err := authr.Authenticate(ctx, to)
+       if err != nil {
+               return nil, err
+       }
+
+       m, ok := claims.(map[string]interface{})
+       if !ok {
+               openlog.Error("claims convert failed", 
openlog.WithErr(rbacmodel.ErrConvert))
+               return nil, rbacmodel.ErrConvert
+       }
+       account, err := rbacmodel.GetAccount(m)
+       if err != nil {
+               openlog.Error("get account from token failed", 
openlog.WithErr(err))
+               return nil, err
+       }
+       if len(account.Roles) == 0 {
+               openlog.Error("no role found in token")
+               return nil, ErrNoRoles
+       }
+
+       err = accountExist(ctx, account.Name)
+       if err != nil {
+               return nil, err
+       }
+
+       return account, nil
+}
+
+func accountExist(ctx context.Context, user string) error {
+       // if root should pass, cause of root initialization
+       if user == RootName {
+               return nil
+       }
+       exist, err := dbacInstance.AccountExist(ctx, user)
+       if err != nil {
+               return err
+       }
+       if !exist {
+               msg := fmt.Sprintf("account [%s] is deleted", user)
+               return 
rbacmodel.NewError(rbacmodel.ErrTokenOwnedAccountDeleted, msg)
+       }
+       return nil
+}
+
+func filterRoles(roleList []string) (hasAdmin bool, normalRoles []string) {
+       for _, r := range roleList {
+               if r == rbacmodel.RoleAdmin {
+                       hasAdmin = true
+                       return
+               }
+               normalRoles = append(normalRoles, r)
+       }
+       return
+}
diff --git a/server/datasource/auth/checkKVPerm.go 
b/server/datasource/auth/checkKVPerm.go
new file mode 100644
index 0000000..87e3c74
--- /dev/null
+++ b/server/datasource/auth/checkKVPerm.go
@@ -0,0 +1,75 @@
+package auth
+
+import (
+       "context"
+       "github.com/apache/servicecomb-kie/pkg/model"
+)
+
+func CheckGetOneKV(ctx context.Context, kv *model.KVDoc) error {
+       var labelsList []map[string]string
+       if kv.Labels != nil {
+               labelsList = append(labelsList, kv.Labels)
+       }
+       scop := &ResourceScope{
+               Type:   "config",
+               Verb:   "get",
+               Labels: labelsList,
+       }
+       _, err := CheckPermByReq(ctx, scop)
+       return err
+}
+
+func FilterKVList(ctx context.Context, kvs []*model.KVDoc) ([]*model.KVDoc, 
error) {
+       scop := &ResourceScope{
+               Type: "config",
+               Verb: "get",
+       }
+       labels, err := CheckPermByReq(ctx, scop)
+       if err != nil {
+               return nil, err
+       }
+
+       return FilterKV(kvs, labels), err
+}
+
+func CheckCreateOneKV(ctx context.Context, kv *model.KVDoc) error {
+       var labelsList []map[string]string
+       if kv.Labels != nil {
+               labelsList = append(labelsList, kv.Labels)
+       }
+       scop := &ResourceScope{
+               Type:   "config",
+               Verb:   "create",
+               Labels: labelsList,
+       }
+       _, err := CheckPermByReq(ctx, scop)
+       return err
+}
+
+func CheckDeleteOneKV(ctx context.Context, kv *model.KVDoc) error {
+       var labelsList []map[string]string
+       if kv.Labels != nil {
+               labelsList = append(labelsList, kv.Labels)
+       }
+       scop := &ResourceScope{
+               Type:   "config",
+               Verb:   "delete",
+               Labels: labelsList,
+       }
+       _, err := CheckPermByReq(ctx, scop)
+       return err
+}
+
+func CheckUpdateOneKV(ctx context.Context, kv *model.KVDoc) error {
+       var labelsList []map[string]string
+       if kv.Labels != nil {
+               labelsList = append(labelsList, kv.Labels)
+       }
+       scop := &ResourceScope{
+               Type:   "config",
+               Verb:   "update",
+               Labels: labelsList,
+       }
+       _, err := CheckPermByReq(ctx, scop)
+       return err
+}
diff --git a/server/datasource/auth/decision.go 
b/server/datasource/auth/decision.go
new file mode 100644
index 0000000..ae4f639
--- /dev/null
+++ b/server/datasource/auth/decision.go
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 auth
+
+import (
+       "context"
+       "fmt"
+
+       rbacmodel "github.com/go-chassis/cari/rbac"
+       "github.com/go-chassis/openlog"
+)
+
+// Allow return: matched labels(empty if no label defined), error
+func Allow(ctx context.Context, roleList []string,
+       targetResource *ResourceScope) ([]map[string]string, error) {
+       //TODO check project
+       allPerms, err := getPermsByRoles(ctx, roleList)
+       if err != nil {
+               openlog.Error("get role list errors", openlog.WithErr(err))
+               return nil, err
+       }
+       if len(allPerms) == 0 {
+               openlog.Warn("role list has no any permissions")
+               return nil, rbacmodel.NewError(rbacmodel.ErrNoPermission, "role 
has no any permissions")
+       }
+       allow, labelList := GetLabel(allPerms, targetResource.Type, 
targetResource.Verb)
+       if !allow {
+               return nil, rbacmodel.NewError(rbacmodel.ErrNoPermission,
+                       fmt.Sprintf("role has no permissions[%s:%s]", 
targetResource.Type, targetResource.Verb))
+       }
+       // allow, but no label found, means we can ignore the labels
+       if len(labelList) == 0 {
+               return nil, nil
+       }
+       // target resource needs no label, return without filter
+       if len(targetResource.Labels) == 0 {
+               return labelList, nil
+       }
+       // allow, and labels found, filter the labels
+       filteredLabelList := FilterLabel(targetResource.Labels, labelList)
+       // target resource label matches no label in permission, means not allow
+       if len(filteredLabelList) == 0 {
+               return nil, rbacmodel.NewError(rbacmodel.ErrNoPermission,
+                       fmt.Sprintf("role has no permissions[%s:%s] for labels 
%v",
+                               targetResource.Type, targetResource.Verb, 
targetResource.Labels))
+       }
+       return filteredLabelList, nil
+}
+
+func FilterLabel(targetResourceLabel []map[string]string, permLabelList 
[]map[string]string) []map[string]string {
+       l := make([]map[string]string, 0)
+       for _, resourceLabel := range targetResourceLabel {
+               for _, label := range permLabelList {
+                       if LabelMatched(resourceLabel, label) {
+                               l = append(l, label)
+                       }
+               }
+       }
+       return l
+}
+
+func LabelMatched(targetResourceLabel map[string]string, permLabel 
map[string]string) bool {
+       for k, v := range permLabel {
+               if vv := targetResourceLabel[k]; vv != v {
+                       return false
+               }
+       }
+       return true
+}
+
+func getPermsByRoles(ctx context.Context, roleList []string) 
([]*rbacmodel.Permission, error) {
+       var allPerms = make([]*rbacmodel.Permission, 0)
+       for _, name := range roleList {
+               r, err := dbacInstance.GetRole(ctx, name)
+               if err == nil {
+                       allPerms = append(allPerms, r.Perms...)
+                       continue
+               }
+               if err.Error() == "role not exist" {
+                       openlog.Warn(fmt.Sprintf("role [%s] not exist", name))
+                       continue
+               }
+               openlog.Error(fmt.Sprintf("get role [%s] failed", name), 
openlog.WithErr(err))
+               return nil, err
+       }
+       return allPerms, nil
+}
+
+// GetLabel checks if the perms have permission to operate the resource(ignore 
label),
+// if one perm have the permission, add it's label to the result.
+func GetLabel(perms []*rbacmodel.Permission, targetResource, verb string) 
(allow bool, labelList []map[string]string) {
+       for _, perm := range perms {
+               a, l := GetLabelFromSinglePerm(perm, targetResource, verb)
+               if !a {
+                       continue
+               }
+               allow = true
+               // allow and has no label, return fast
+               if len(l) == 0 {
+                       return true, nil
+               }
+               labelList = append(labelList, l...)
+       }
+       return
+}
+
+// GetLabel checks if the perm have permission to operate the resource(ignore 
label),
+// if the perm have the permission, return it's label.
+func GetLabelFromSinglePerm(perm *rbacmodel.Permission, targetResource, verb 
string) (allow bool, labelList []map[string]string) {
+       if !allowVerb(perm.Verbs, verb) {
+               return false, nil
+       }
+
+       return getResourceLabel(perm.Resources, targetResource)
+}
+
+func allowVerb(haystack []string, needle string) bool {
+       for _, e := range haystack {
+               if e == "*" || e == needle {
+                       return true
+               }
+       }
+       return false
+}
+
+func getResourceLabel(resources []*rbacmodel.Resource, needle string) (allow 
bool, labelList []map[string]string) {
+       for _, resource := range resources {
+               // filter the same resource
+               if resource.Type != needle {
+                       continue
+               }
+               // has no label, return fast
+               if len(resource.Labels) == 0 {
+                       return true, nil
+               }
+               labelList = append(labelList, resource.Labels)
+               allow = true
+       }
+       return
+}
diff --git a/server/datasource/auth/filter_kvdoc.go 
b/server/datasource/auth/filter_kvdoc.go
new file mode 100644
index 0000000..7c4fd13
--- /dev/null
+++ b/server/datasource/auth/filter_kvdoc.go
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 auth
+
+import "github.com/apache/servicecomb-kie/pkg/model"
+
+func FilterKV(kvs []*model.KVDoc, labelsList []map[string]string) 
[]*model.KVDoc {
+       var permKVs []*model.KVDoc
+       for _, kv := range kvs {
+               for _, labels := range labelsList {
+                       if !matchOne(kv, labels) {
+                               continue
+                       }
+                       permKVs = append(permKVs, kv)
+                       break
+               }
+       }
+       return permKVs
+}
+
+func matchOne(kv *model.KVDoc, labels map[string]string) bool {
+       for lk, lv := range labels {
+               if v, ok := kv.Labels[lk]; ok && v != lv {
+                       return false
+               }
+       }
+       return true
+}
+
+func MatchLabelsList(kv *model.KVDoc, labelsList []map[string]string) bool {
+       for _, labels := range labelsList {
+               if !matchOne(kv, labels) {
+                       continue
+               }
+               return true
+       }
+       return false
+}
diff --git a/server/datasource/auth/makeScope.go 
b/server/datasource/auth/makeScope.go
new file mode 100644
index 0000000..0dbba46
--- /dev/null
+++ b/server/datasource/auth/makeScope.go
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 auth
+
+// ResourceScope is the resource scope parsed from request
+type ResourceScope struct {
+       Type string
+       // Labels is a map used to filter resource permissions during pre 
verification.
+       // If a key of permission set is missing in the Labels, pre 
verification will pass this key
+       Labels []map[string]string
+       // Verb is the apply resource action, e.g. "get", "create"
+       Verb string
+}
diff --git a/server/datasource/auth/perm.go b/server/datasource/auth/perm.go
new file mode 100644
index 0000000..98ca846
--- /dev/null
+++ b/server/datasource/auth/perm.go
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 auth
+
+import (
+       "context"
+)
+
+func CheckPermByReq(ctx context.Context, targetResource *ResourceScope) 
([]map[string]string, error) {
+       account, err := GetAccountFromReq(ctx)
+       if err != nil {
+               return nil, err
+       }
+
+       hasAdmin, normalRoles := filterRoles(account.Roles)
+       if hasAdmin {
+               return nil, nil
+       }
+
+       return Allow(ctx, normalRoles, targetResource)
+}
diff --git a/server/datasource/auth/rbac.go b/server/datasource/auth/rbac.go
new file mode 100644
index 0000000..76cc3eb
--- /dev/null
+++ b/server/datasource/auth/rbac.go
@@ -0,0 +1,36 @@
+package auth
+
+import (
+       "context"
+       "github.com/apache/servicecomb-kie/pkg/util"
+       crbac "github.com/go-chassis/cari/rbac"
+       "github.com/go-chassis/go-chassis/v2/server/restful"
+       "net/http"
+       "os"
+
+       etcd "github.com/apache/servicecomb-kie/server/datasource/etcd/rbac"
+       mongo "github.com/apache/servicecomb-kie/server/datasource/mongo/rbac"
+)
+
+type DBAC_DB interface {
+       GetRole(ctx context.Context, name string) (*crbac.Role, error)
+       GenerateRBACRoleKey(name string) string
+       AccountExist(ctx context.Context, name string) (bool, error)
+       GenerateRBACAccountKey(name string) string
+}
+
+var dbacInstance DBAC_DB
+
+func init() {
+       d := os.Getenv("DBAC_DB")
+       if d == "mongodb" {
+               dbacInstance = &mongo.RBAC_Mongo{}
+       } else {
+               dbacInstance = &etcd.RBAC_ETCD{}
+       }
+}
+
+func SetContext(req *http.Request) {
+       v := req.Header.Get(restful.HeaderAuth)
+       util.SetRequestContext(req, restful.HeaderAuth, v)
+}
diff --git a/server/datasource/etcd/kv/kv_dao.go 
b/server/datasource/etcd/kv/kv_dao.go
index 6296e08..e97a06c 100644
--- a/server/datasource/etcd/kv/kv_dao.go
+++ b/server/datasource/etcd/kv/kv_dao.go
@@ -20,6 +20,7 @@ package kv
 import (
        "context"
        "encoding/json"
+       "github.com/apache/servicecomb-kie/server/datasource/auth"
        "regexp"
        "strings"
 
@@ -38,6 +39,11 @@ type Dao struct {
 }
 
 func (s *Dao) Create(ctx context.Context, kv *model.KVDoc, options 
...datasource.WriteOption) (*model.KVDoc, error) {
+       //rbac
+       if err := auth.CheckCreateOneKV(ctx, kv); err != nil {
+               return nil, err
+       }
+
        opts := datasource.NewWriteOptions(options...)
        var exist bool
        var err error
@@ -116,6 +122,12 @@ func (s *Dao) Update(ctx context.Context, kv *model.KVDoc, 
options ...datasource
                openlog.Error(err.Error())
                return err
        }
+
+       //rbac
+       if err := auth.CheckUpdateOneKV(ctx, &oldKV); err != nil {
+               return err
+       }
+
        oldKV.LabelFormat = kv.LabelFormat
        oldKV.Value = kv.Value
        oldKV.Status = kv.Status
@@ -215,6 +227,12 @@ func (s *Dao) FindOneAndDelete(ctx context.Context, kvID, 
project, domain string
 func findOneAndDelete(ctx context.Context, kvID, project, domain string) 
(*model.KVDoc, error) {
        kvKey := key.KV(domain, project, kvID)
        kvDoc := model.KVDoc{}
+
+       //rbac check
+       if _, err := getKVDoc(ctx, domain, project, kvID); err != nil {
+               return nil, err
+       }
+
        resp, err := etcdadpt.ListAndDelete(ctx, kvKey)
        if err != nil {
                openlog.Error("delete Key error: " + err.Error())
@@ -269,7 +287,7 @@ func txnFindOneAndDelete(ctx context.Context, kvID, 
project, domain string) (*mo
        return kvDoc, nil
 }
 
-// getKVDoc is to get kv
+// getKVDoc is to get kv for delete
 func getKVDoc(ctx context.Context, domain, project, kvID string) 
(*model.KVDoc, error) {
        resp, err := etcdadpt.Get(ctx, key.KV(domain, project, kvID))
        if err != nil {
@@ -285,6 +303,12 @@ func getKVDoc(ctx context.Context, domain, project, kvID 
string) (*model.KVDoc,
                openlog.Error("decode error: " + err.Error())
                return nil, err
        }
+
+       //rbac
+       if err := auth.CheckDeleteOneKV(ctx, curKV); err != nil {
+               return nil, err
+       }
+
        return curKV, nil
 }
 
@@ -302,6 +326,9 @@ func findManyAndDelete(ctx context.Context, kvIDs []string, 
project, domain stri
        var docs []*model.KVDoc
        var opOptions []etcdadpt.OpOptions
        for _, id := range kvIDs {
+               if _, err := getKVDoc(ctx, domain, project, id); err != nil {
+                       continue
+               }
                opOptions = append(opOptions, 
etcdadpt.OpDel(etcdadpt.WithStrKey(key.KV(domain, project, id))))
        }
        resp, err := etcdadpt.ListAndDeleteMany(ctx, opOptions...)
@@ -412,6 +439,12 @@ func (s *Dao) Get(ctx context.Context, req 
*model.GetKVRequest) (*model.KVDoc, e
                openlog.Error("decode error: " + err.Error())
                return nil, err
        }
+
+       //rbac
+       if err := auth.CheckGetOneKV(ctx, curKV); err != nil {
+               return nil, err
+       }
+
        return curKV, nil
 }
 
@@ -465,6 +498,14 @@ func (s *Dao) List(ctx context.Context, project, domain 
string, options ...datas
                        break
                }
        }
+
+       filterKVs, err := auth.FilterKVList(ctx, result.Data)
+       if err != nil {
+               return nil, err
+       }
+
+       result.Data = filterKVs
+
        return pagingResult(result, opts), nil
 }
 
diff --git a/server/datasource/etcd/rbac/role.go 
b/server/datasource/etcd/rbac/role.go
new file mode 100644
index 0000000..204b6ac
--- /dev/null
+++ b/server/datasource/etcd/rbac/role.go
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 rbac
+
+import (
+       "context"
+       "encoding/json"
+       "errors"
+       "github.com/go-chassis/openlog"
+
+       crbac "github.com/go-chassis/cari/rbac"
+       "github.com/little-cui/etcdadpt"
+)
+
+type RBAC_ETCD struct {
+}
+
+func (re *RBAC_ETCD) GetRole(ctx context.Context, name string) (*crbac.Role, 
error) {
+       kv, err := etcdadpt.Get(ctx, re.GenerateRBACRoleKey(name))
+       if err != nil {
+               return nil, err
+       }
+       if kv == nil {
+               return nil, errors.New("role not exist")
+       }
+       role := &crbac.Role{}
+       err = json.Unmarshal(kv.Value, role)
+       if err != nil {
+               openlog.Error("role info format invalid", openlog.WithErr(err))
+               return nil, err
+       }
+       return role, nil
+}
+
+func (re *RBAC_ETCD) GenerateRBACRoleKey(name string) string {
+       return "/cse-sr/roles/" + name
+}
+
+func (re *RBAC_ETCD) AccountExist(ctx context.Context, name string) (bool, 
error) {
+       return etcdadpt.Exist(ctx, re.GenerateRBACAccountKey(name))
+}
+
+func (re *RBAC_ETCD) GenerateRBACAccountKey(name string) string {
+       return "/cse-sr/accounts/" + name
+}
diff --git a/server/datasource/mongo/rbac/role.go 
b/server/datasource/mongo/rbac/role.go
new file mode 100644
index 0000000..705666a
--- /dev/null
+++ b/server/datasource/mongo/rbac/role.go
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 rbac
+
+import (
+       "context"
+       "encoding/json"
+       "errors"
+       "github.com/go-chassis/openlog"
+
+       crbac "github.com/go-chassis/cari/rbac"
+       "github.com/little-cui/etcdadpt"
+)
+
+type RBAC_Mongo struct {
+}
+
+func (rm *RBAC_Mongo) GetRole(ctx context.Context, name string) (*crbac.Role, 
error) {
+       kv, err := etcdadpt.Get(ctx, rm.GenerateRBACRoleKey(name))
+       if err != nil {
+               return nil, err
+       }
+       if kv == nil {
+               return nil, errors.New("role not exist")
+       }
+       role := &crbac.Role{}
+       err = json.Unmarshal(kv.Value, role)
+       if err != nil {
+               openlog.Error("role info format invalid", openlog.WithErr(err))
+               return nil, err
+       }
+       return role, nil
+}
+
+func (rm *RBAC_Mongo) GenerateRBACRoleKey(name string) string {
+       return "/cse-sr/roles/" + name
+}
+
+func (rm *RBAC_Mongo) AccountExist(ctx context.Context, name string) (bool, 
error) {
+       return etcdadpt.Exist(ctx, rm.GenerateRBACAccountKey(name))
+}
+
+func (rm *RBAC_Mongo) GenerateRBACAccountKey(name string) string {
+       return "/cse-sr/accounts/" + name
+}
diff --git a/server/datasource/rbac/rbac.go b/server/datasource/rbac/rbac.go
new file mode 100644
index 0000000..a017ec0
--- /dev/null
+++ b/server/datasource/rbac/rbac.go
@@ -0,0 +1,13 @@
+package rbac
+
+import (
+       "context"
+       crbac "github.com/go-chassis/cari/rbac"
+)
+
+type DBAC_DB interface {
+       GetRole(ctx context.Context, name string) (*crbac.Role, error)
+       GenerateRBACRoleKey(name string) string
+       AccountExist(ctx context.Context, name string) (bool, error)
+       GenerateRBACAccountKey(name string) string
+}
diff --git a/server/rbac/rbac.go b/server/rbac/rbac.go
index b7614fa..8d38c74 100644
--- a/server/rbac/rbac.go
+++ b/server/rbac/rbac.go
@@ -18,6 +18,7 @@
 package rbac
 
 import (
+       "github.com/apache/servicecomb-kie/server/datasource/auth"
        "net/http"
        "os"
        "path/filepath"
@@ -67,6 +68,7 @@ func Init() {
                        newReq := 
req.WithContext(rbac.NewContext(req.Context(), payload))
                        *req = *newReq
                        //TODO role perm check
+                       auth.SetContext(req)
                        return nil
                },
        })
diff --git a/server/resource/v1/kv_resource.go 
b/server/resource/v1/kv_resource.go
index 1fc087a..55126d5 100644
--- a/server/resource/v1/kv_resource.go
+++ b/server/resource/v1/kv_resource.go
@@ -91,6 +91,7 @@ func (r *KVResource) Post(rctx *restful.Context) {
 // Put update a kv
 func (r *KVResource) Put(rctx *restful.Context) {
        var err error
+
        kvID := rctx.ReadPathParameter(common.PathParamKVID)
        project := rctx.ReadPathParameter(common.PathParameterProject)
        kvReq := new(model.UpdateKVRequest)


Reply via email to