This is an automated email from the ASF dual-hosted git repository.
littlecui pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-service-center.git
The following commit(s) were added to refs/heads/master by this push:
new 7f9ac68 role binding check (#1027)
7f9ac68 is described below
commit 7f9ac68d6a91b42b87f9673bb7ab692896956db3
Author: Shawn <[email protected]>
AuthorDate: Mon May 31 17:06:35 2021 +0800
role binding check (#1027)
---
datasource/account.go | 1 +
datasource/account_test.go | 4 +--
datasource/etcd/account.go | 48 ++++++++++++++++++++++++++++----
datasource/etcd/path/key_generator.go | 15 +++++++++-
datasource/etcd/role.go | 16 +++++++++++
datasource/mongo/client/mongo.go | 38 ++++++++++++++++++++++++-
datasource/mongo/role.go | 8 ++++--
examples/etcd_data_struct.yaml | 4 ++-
server/resource/v4/role_resource.go | 6 ++++
server/resource/v4/role_resource_test.go | 38 +++++++++++++++++++------
10 files changed, 157 insertions(+), 21 deletions(-)
diff --git a/datasource/account.go b/datasource/account.go
index 0c66730..89f5c63 100644
--- a/datasource/account.go
+++ b/datasource/account.go
@@ -31,6 +31,7 @@ var (
ErrDeleteAccountFailed = errors.New("failed to delete account")
ErrQueryAccountFailed = errors.New("failed to query account")
ErrAccountNotExist = errors.New("account not exist")
+ ErrRoleBindingExist = errors.New("role is bind to account")
)
// AccountManager contains the RBAC CRUD
diff --git a/datasource/account_test.go b/datasource/account_test.go
index b61845d..759ad0f 100644
--- a/datasource/account_test.go
+++ b/datasource/account_test.go
@@ -83,9 +83,9 @@ func TestAccount(t *testing.T) {
a2.Password = "new-password"
err = datasource.Instance().UpdateAccount(context.Background(),
a2.Name, &a2)
assert.NoError(t, err)
- _, n, err :=
datasource.Instance().ListAccount(context.Background())
+ accounts, _, err :=
datasource.Instance().ListAccount(context.Background())
assert.NoError(t, err)
- assert.Equal(t, int64(2), n)
+ assert.GreaterOrEqual(t, len(accounts), 2)
_, err =
datasource.Instance().DeleteAccount(context.Background(), []string{a1.Name})
assert.NoError(t, err)
_, err =
datasource.Instance().DeleteAccount(context.Background(), []string{a2.Name})
diff --git a/datasource/etcd/account.go b/datasource/etcd/account.go
index 99bb296..bcf37e4 100644
--- a/datasource/etcd/account.go
+++ b/datasource/etcd/account.go
@@ -19,6 +19,7 @@ import (
"context"
"encoding/json"
"fmt"
+ "github.com/go-chassis/foundation/stringutil"
"strconv"
"time"
@@ -44,7 +45,6 @@ func (ds *DataSource) CreateAccount(ctx context.Context, a
*rbac.Account) error
log.Errorf(err, "can not release account lock")
}
}()
- name := path.GenerateRBACAccountKey(a.Name)
exist, err := datasource.Instance().AccountExist(ctx, a.Name)
if err != nil {
log.Errorf(err, "can not save account info")
@@ -63,12 +63,12 @@ func (ds *DataSource) CreateAccount(ctx context.Context, a
*rbac.Account) error
a.ID = util.GenerateUUID()
a.CreateTime = strconv.FormatInt(time.Now().Unix(), 10)
a.UpdateTime = a.CreateTime
- value, err := json.Marshal(a)
+ opts, err := GenAccountOpts(a, client.ActionPut)
if err != nil {
- log.Errorf(err, "account info is invalid")
+ log.Error("", err)
return err
}
- err = client.PutBytes(ctx, name, value)
+ err = client.BatchCommit(ctx, opts)
if err != nil {
log.Errorf(err, "can not save account info")
return err
@@ -76,7 +76,28 @@ func (ds *DataSource) CreateAccount(ctx context.Context, a
*rbac.Account) error
log.Info("create new account: " + a.ID)
return nil
}
+func GenAccountOpts(a *rbac.Account, action client.ActionType)
([]client.PluginOp, error) {
+ opts := make([]client.PluginOp, 0)
+ value, err := json.Marshal(a)
+ if err != nil {
+ log.Errorf(err, "account info is invalid")
+ return nil, err
+ }
+ opts = append(opts, client.PluginOp{
+ Key: stringutil.Str2bytes(path.GenerateAccountKey(a.Name)),
+ Value: value,
+ Action: action,
+ })
+ for _, r := range a.Roles {
+ opt := client.PluginOp{
+ Key:
stringutil.Str2bytes(path.GenRoleAccountIdxKey(r, a.Name)),
+ Action: action,
+ }
+ opts = append(opts, opt)
+ }
+ return opts, nil
+}
func (ds *DataSource) AccountExist(ctx context.Context, name string) (bool,
error) {
resp, err := client.Instance().Do(ctx, client.GET,
client.WithStrKey(path.GenerateRBACAccountKey(name)))
@@ -142,8 +163,23 @@ func (ds *DataSource) DeleteAccount(ctx context.Context,
names []string) (bool,
return false, nil
}
for _, name := range names {
- _, err := client.Instance().Do(ctx, client.DEL,
- client.WithStrKey(path.GenerateRBACAccountKey(name)))
+ a, err := ds.GetAccount(ctx, name)
+ if err != nil {
+ log.Error("", err)
+ continue //do not fail if some account is invalid
+
+ }
+ if a == nil {
+ log.Warn("can not find account")
+ continue
+ }
+ opts, err := GenAccountOpts(a, client.ActionDelete)
+ if err != nil {
+ log.Error("", err)
+ continue //do not fail if some account is invalid
+
+ }
+ err = client.BatchCommit(ctx, opts)
if err != nil {
log.Error(datasource.ErrDeleteAccountFailed.Error(),
err)
return false, err
diff --git a/datasource/etcd/path/key_generator.go
b/datasource/etcd/path/key_generator.go
index 8d6ce8b..f010a1a 100644
--- a/datasource/etcd/path/key_generator.go
+++ b/datasource/etcd/path/key_generator.go
@@ -76,7 +76,20 @@ func GenerateRBACRoleKey(name string) string {
name,
}, SPLIT)
}
-
+func GenRoleAccountIdxKey(role, account string) string {
+ return util.StringJoin([]string{
+ GetRootKey(),
+ "idx-role-account",
+ role, account,
+ }, SPLIT)
+}
+func GenRoleAccountPrefixIdxKey(role string) string {
+ return util.StringJoin([]string{
+ GetRootKey(),
+ "idx-role-account",
+ role,
+ }, SPLIT)
+}
func GenerateETCDProjectKey(domain, project string) string {
return util.StringJoin([]string{
GetProjectRootKey(domain),
diff --git a/datasource/etcd/role.go b/datasource/etcd/role.go
index 1cd803c..867f035 100644
--- a/datasource/etcd/role.go
+++ b/datasource/etcd/role.go
@@ -122,6 +122,14 @@ func (ds *DataSource) ListRole(ctx context.Context)
([]*rbac.Role, int64, error)
return roles, resp.Count, nil
}
func (ds *DataSource) DeleteRole(ctx context.Context, name string) (bool,
error) {
+ exists, err := RoleBindingExists(ctx, name)
+ if err != nil {
+ log.Error("", err)
+ return false, err
+ }
+ if exists {
+ return false, datasource.ErrRoleBindingExist
+ }
resp, err := client.Instance().Do(ctx, client.DEL,
client.WithStrKey(path.GenerateRBACRoleKey(name)))
if err != nil {
@@ -129,6 +137,14 @@ func (ds *DataSource) DeleteRole(ctx context.Context, name
string) (bool, error)
}
return resp.Succeeded, nil
}
+func RoleBindingExists(ctx context.Context, role string) (bool, error) {
+ _, total, err := client.List(ctx, path.GenRoleAccountPrefixIdxKey(role))
+ if err != nil {
+ log.Error("", err)
+ return false, err
+ }
+ return total > 0, nil
+}
func (ds *DataSource) UpdateRole(ctx context.Context, name string, role
*rbac.Role) error {
role.UpdateTime = strconv.FormatInt(time.Now().Unix(), 10)
value, err := json.Marshal(role)
diff --git a/datasource/mongo/client/mongo.go b/datasource/mongo/client/mongo.go
index 4f7e761..55dec95 100644
--- a/datasource/mongo/client/mongo.go
+++ b/datasource/mongo/client/mongo.go
@@ -61,7 +61,15 @@ type MongoOperation struct {
func GetMongoClient() *MongoClient {
return mc
}
-
+func DeleteDoc(ctx context.Context, Table string, filter interface{}, opts
...*options.DeleteOptions) (*mongo.DeleteResult, error) {
+ return mc.Delete(ctx, Table, filter, opts...)
+}
+func Find(ctx context.Context, Table string, filter interface{}, opts
...*options.FindOptions) (*mongo.Cursor, error) {
+ return mc.Find(ctx, Table, filter, opts...)
+}
+func Count(ctx context.Context, Table string, filter interface{}, opts
...*options.CountOptions) (int64, error) {
+ return mc.db.Collection(Table).CountDocuments(ctx, filter, opts...)
+}
func NewMongoClient(config storage.Options) {
inst := &MongoClient{}
if err := inst.Initialize(config); err != nil {
@@ -85,6 +93,34 @@ func (mc *MongoClient) Initialize(config storage.Options)
(err error) {
return nil
}
+// ExecTxn execute a transaction command
+// want to abort transaction, return error in cmd fn impl, otherwise it will
commit transaction
+func (mc *MongoClient) ExecTxn(ctx context.Context, cmd func(sessionContext
mongo.SessionContext) error) error {
+ session, err := mc.client.StartSession()
+ if err != nil {
+ return err
+ }
+ if err = session.StartTransaction(); err != nil {
+ return err
+ }
+ defer session.EndSession(ctx)
+ if err = mongo.WithSession(ctx, session, func(sc mongo.SessionContext)
error {
+ if err = cmd(sc); err != nil {
+ if err = session.AbortTransaction(sc); err != nil {
+ return err
+ }
+ return nil
+ } else {
+ if err = session.CommitTransaction(sc); err != nil {
+ return err
+ }
+ return nil
+ }
+ }); err != nil {
+ return err
+ }
+ return nil
+}
func (mc *MongoClient) Err() <-chan error {
return mc.err
}
diff --git a/datasource/mongo/role.go b/datasource/mongo/role.go
index 7ab4ce6..c4ab393 100644
--- a/datasource/mongo/role.go
+++ b/datasource/mongo/role.go
@@ -19,8 +19,8 @@ package mongo
import (
"context"
-
"github.com/go-chassis/cari/rbac"
+ "go.mongodb.org/mongo-driver/bson"
"github.com/apache/servicecomb-service-center/datasource"
"github.com/apache/servicecomb-service-center/datasource/mongo/client"
@@ -102,8 +102,12 @@ func (ds *DataSource) ListRole(ctx context.Context)
([]*rbac.Role, int64, error)
}
func (ds *DataSource) DeleteRole(ctx context.Context, name string) (bool,
error) {
+ n, err := client.Count(ctx, model.CollectionAccount, bson.M{"roles":
bson.M{"$in": []string{name}}})
+ if n > 0 {
+ return false, datasource.ErrRoleBindingExist
+ }
filter := mutil.NewFilter(mutil.RoleName(name))
- result, err := client.GetMongoClient().Delete(ctx,
model.CollectionRole, filter)
+ result, err := client.DeleteDoc(ctx, model.CollectionRole, filter)
if err != nil {
return false, err
}
diff --git a/examples/etcd_data_struct.yaml b/examples/etcd_data_struct.yaml
index 13c98d7..bb18424 100644
--- a/examples/etcd_data_struct.yaml
+++ b/examples/etcd_data_struct.yaml
@@ -130,7 +130,9 @@
"currentPassword": "password",
"status": "normal"
}
-
+# record role binding to account
+/cse-sr/idx-role-account/{role}/{account}:
+ {no value}
# domain
# /cse-sr/domains/{domain}
/cse-sr/domains/default:
diff --git a/server/resource/v4/role_resource.go
b/server/resource/v4/role_resource.go
index b768207..17e363b 100644
--- a/server/resource/v4/role_resource.go
+++ b/server/resource/v4/role_resource.go
@@ -19,6 +19,8 @@ package v4
import (
"encoding/json"
+ "errors"
+ "github.com/apache/servicecomb-service-center/datasource"
"io/ioutil"
"net/http"
@@ -138,6 +140,10 @@ func (rr *RoleResource) DeleteRole(w http.ResponseWriter,
req *http.Request) {
n := req.URL.Query().Get(":roleName")
status, err := dao.DeleteRole(req.Context(), n)
+ if errors.Is(err, datasource.ErrRoleBindingExist) {
+ rest.WriteError(w, discovery.ErrInvalidParams, errorsEx.MsgJSON)
+ return
+ }
if err != nil {
log.Error(errorsEx.MsgJSON, err)
rest.WriteError(w, discovery.ErrInternal, errorsEx.MsgJSON)
diff --git a/server/resource/v4/role_resource_test.go
b/server/resource/v4/role_resource_test.go
index 76ea993..4598496 100644
--- a/server/resource/v4/role_resource_test.go
+++ b/server/resource/v4/role_resource_test.go
@@ -18,11 +18,12 @@ import (
)
func TestRoleResource_CreateOrUpdateRole(t *testing.T) {
- var to = &rbacmodel.Token{}
+ var superToken = &rbacmodel.Token{}
ctx := context.TODO()
dao.DeleteAccount(ctx, "dev_test")
+ dao.DeleteAccount(ctx, "dev_test2")
dao.DeleteRole(ctx, "tester")
- t.Run("root login", func(t *testing.T) {
+ t.Run("root login,to get super token", func(t *testing.T) {
b, _ := json.Marshal(&rbacmodel.Account{Name: "root", Password:
"Complicated_password1"})
r, err := http.NewRequest(http.MethodPost, "/v4/token",
bytes.NewBuffer(b))
@@ -30,14 +31,14 @@ func TestRoleResource_CreateOrUpdateRole(t *testing.T) {
w := httptest.NewRecorder()
rest.GetRouter().ServeHTTP(w, r)
assert.Equal(t, http.StatusOK, w.Code)
- json.Unmarshal(w.Body.Bytes(), to)
+ json.Unmarshal(w.Body.Bytes(), superToken)
})
t.Run("create account dev_test and add a role", func(t *testing.T) {
b, _ := json.Marshal(&rbacmodel.Account{Name: "dev_test",
Password: "Complicated_password3", Roles: []string{"tester"}})
r, _ := http.NewRequest(http.MethodPost, "/v4/accounts",
bytes.NewBuffer(b))
- r.Header.Set(restful.HeaderAuth, "Bearer "+to.TokenStr)
+ r.Header.Set(restful.HeaderAuth, "Bearer "+superToken.TokenStr)
w := httptest.NewRecorder()
rest.GetRouter().ServeHTTP(w, r)
assert.Equal(t, http.StatusOK, w.Code)
@@ -64,13 +65,13 @@ func TestRoleResource_CreateOrUpdateRole(t *testing.T) {
})
r2, _ := http.NewRequest(http.MethodPost, "/v4/roles",
bytes.NewReader(b2))
- r2.Header.Set(restful.HeaderAuth, "Bearer "+to.TokenStr)
+ r2.Header.Set(restful.HeaderAuth, "Bearer "+superToken.TokenStr)
w2 := httptest.NewRecorder()
rest.GetRouter().ServeHTTP(w2, r2)
assert.Equal(t, http.StatusOK, w2.Code)
r3, _ := http.NewRequest(http.MethodGet, "/v4/roles", nil)
- r3.Header.Set(restful.HeaderAuth, "Bearer "+to.TokenStr)
+ r3.Header.Set(restful.HeaderAuth, "Bearer "+superToken.TokenStr)
w3 := httptest.NewRecorder()
rest.GetRouter().ServeHTTP(w3, r3)
assert.Equal(t, http.StatusOK, w3.Code)
@@ -85,13 +86,13 @@ func TestRoleResource_CreateOrUpdateRole(t *testing.T) {
},
})
r4, _ := http.NewRequest(http.MethodPut, "/v4/roles/tester",
bytes.NewReader(b4))
- r4.Header.Set(restful.HeaderAuth, "Bearer "+to.TokenStr)
+ r4.Header.Set(restful.HeaderAuth, "Bearer "+superToken.TokenStr)
w4 := httptest.NewRecorder()
rest.GetRouter().ServeHTTP(w4, r4)
assert.Equal(t, http.StatusOK, w4.Code)
})
- t.Run("Inquire role", func(t *testing.T) {
+ t.Run("get role", func(t *testing.T) {
b, _ := json.Marshal(&rbacmodel.Account{Name: "root", Password:
"Complicated_password1"})
r, _ := http.NewRequest(http.MethodPost, "/v4/token",
bytes.NewBuffer(b))
@@ -124,6 +125,27 @@ func TestRoleResource_CreateOrUpdateRole(t *testing.T) {
rest.GetRouter().ServeHTTP(w3, r3)
assert.Equal(t, http.StatusForbidden, w3.Code)
})
+
+ t.Run("delete tester role, it is bind to account, should fail", func(t
*testing.T) {
+ r3, _ := http.NewRequest(http.MethodDelete, "/v4/roles/tester",
nil)
+ r3.Header.Set(restful.HeaderAuth, "Bearer "+superToken.TokenStr)
+ w3 := httptest.NewRecorder()
+ rest.GetRouter().ServeHTTP(w3, r3)
+ assert.Equal(t, http.StatusBadRequest, w3.Code)
+ })
+ t.Run("delete account, then delete role should success", func(t
*testing.T) {
+ r3, _ := http.NewRequest(http.MethodDelete,
"/v4/accounts/dev_test", nil)
+ r3.Header.Set(restful.HeaderAuth, "Bearer "+superToken.TokenStr)
+ w3 := httptest.NewRecorder()
+ rest.GetRouter().ServeHTTP(w3, r3)
+ assert.Equal(t, http.StatusOK, w3.Code)
+
+ r4, _ := http.NewRequest(http.MethodDelete, "/v4/roles/tester",
nil)
+ r4.Header.Set(restful.HeaderAuth, "Bearer "+superToken.TokenStr)
+ w4 := httptest.NewRecorder()
+ rest.GetRouter().ServeHTTP(w4, r4)
+ assert.Equal(t, http.StatusOK, w4.Code)
+ })
}
func TestRoleResource_MoreRoles(t *testing.T) {
var to = &rbacmodel.Token{}