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 c0df5c36 Feature: add batch create accounts API (#1344)
c0df5c36 is described below
commit c0df5c3666074a63a97af43164ae1764ea9f7eeb
Author: little-cui <[email protected]>
AuthorDate: Sat Oct 22 15:04:52 2022 +0800
Feature: add batch create accounts API (#1344)
---
go.mod | 2 +-
go.sum | 4 +-
pkg/errors/text.go | 21 ++++---
server/resource/rbac/auth_resource.go | 23 +++++++
server/resource/rbac/auth_resource_test.go | 95 ++++++++++++++---------------
server/service/rbac/account_service.go | 28 +++++++++
server/service/rbac/account_service_test.go | 38 ++++++++++++
server/service/validator/rbac_validator.go | 12 +++-
8 files changed, 160 insertions(+), 63 deletions(-)
diff --git a/go.mod b/go.mod
index c6f9fad9..acb41602 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
github.com/cheggaaa/pb v1.0.25
github.com/deckarep/golang-set v1.8.0
github.com/elithrar/simple-scrypt v1.3.0
- github.com/go-chassis/cari v0.8.0
+ github.com/go-chassis/cari v0.9.0
github.com/go-chassis/foundation v0.4.0
github.com/go-chassis/go-archaius v1.5.6
github.com/go-chassis/go-chassis-extension/codec/gojson
v0.0.0-20220816060440-fe98a5615d3f
diff --git a/go.sum b/go.sum
index ae213cfc..01be0afa 100644
--- a/go.sum
+++ b/go.sum
@@ -235,8 +235,8 @@ github.com/go-chassis/cari v0.4.0/go.mod
h1:av/19fqwEP4eOC8unL/z67AAbFDwXUCko6SK
github.com/go-chassis/cari v0.5.0/go.mod
h1:av/19fqwEP4eOC8unL/z67AAbFDwXUCko6SKa4Avrd8=
github.com/go-chassis/cari v0.5.1-0.20210823023004-74041d1363c4/go.mod
h1:av/19fqwEP4eOC8unL/z67AAbFDwXUCko6SKa4Avrd8=
github.com/go-chassis/cari v0.6.0/go.mod
h1:mSDRCOQXGmlD69A6NG0hsv0UP1xbVPtL6HCGI6X1tqs=
-github.com/go-chassis/cari v0.8.0
h1:rtFHRVxOBtdfkXN0KaAxr00F6DAcDcoWZRTMBgOPis8=
-github.com/go-chassis/cari v0.8.0/go.mod
h1:vM13BN0TT505ZKqeJ+hUfzZvfn4nN0vgE6IpBOTWcTc=
+github.com/go-chassis/cari v0.9.0
h1:skvo2PX8nLyu26CCg7qUMv7yP2DY73GrBW9M5tWj63c=
+github.com/go-chassis/cari v0.9.0/go.mod
h1:vM13BN0TT505ZKqeJ+hUfzZvfn4nN0vgE6IpBOTWcTc=
github.com/go-chassis/foundation v0.2.2-0.20201210043510-9f6d3de40234/go.mod
h1:2PjwqpVwYEVaAldl5A58a08viH8p27pNeYaiE3ZxOBA=
github.com/go-chassis/foundation v0.2.2/go.mod
h1:2PjwqpVwYEVaAldl5A58a08viH8p27pNeYaiE3ZxOBA=
github.com/go-chassis/foundation v0.3.0/go.mod
h1:2PjwqpVwYEVaAldl5A58a08viH8p27pNeYaiE3ZxOBA=
diff --git a/pkg/errors/text.go b/pkg/errors/text.go
index ee84eef8..b002e3bb 100644
--- a/pkg/errors/text.go
+++ b/pkg/errors/text.go
@@ -20,14 +20,15 @@ package errors
const (
MsgJSON = "json is invalid"
- MsgOperateAccountFailed = "operate account failed"
- MsgCantOperateRoot = "root can not be operated"
- MsgCantOperateBuildInRole = "build-in role can not be operated"
- MsgCantOperateYour = "your account can not be operated"
- MsgOperateRoleFailed = "operate role failed"
- MsgGetAccountFailed = "get account failed"
- MsgGetRoleFailed = "get role failed"
- MsgRolePerm = "check role permissions failed"
- MsgNoPerm = "no permission to operate"
- MsgListSelfPermsFailed = "list self permissions failed"
+ MsgOperateAccountFailed = "operate account failed"
+ MsgBatchCreateAccountsFailed = "batch create accounts failed"
+ MsgCantOperateRoot = "root can not be operated"
+ MsgCantOperateBuildInRole = "build-in role can not be operated"
+ MsgCantOperateYour = "your account can not be operated"
+ MsgOperateRoleFailed = "operate role failed"
+ MsgGetAccountFailed = "get account failed"
+ MsgGetRoleFailed = "get role failed"
+ MsgRolePerm = "check role permissions failed"
+ MsgNoPerm = "no permission to operate"
+ MsgListSelfPermsFailed = "list self permissions failed"
)
diff --git a/server/resource/rbac/auth_resource.go
b/server/resource/rbac/auth_resource.go
index 91ae358a..cb9245b4 100644
--- a/server/resource/rbac/auth_resource.go
+++ b/server/resource/rbac/auth_resource.go
@@ -45,6 +45,7 @@ func (ar *AuthResource) URLPatterns() []rest.Route {
{Method: http.MethodPost, Path: "/v4/token", Func: ar.Login},
{Method: http.MethodGet, Path: "/v4/self-perms", Func:
ar.ListSelfPerms},
{Method: http.MethodPost, Path: "/v4/accounts", Func:
ar.CreateAccount},
+ {Method: http.MethodPost, Path: "/v4/accounts/batch-create",
Func: ar.BatchCreateAccount},
{Method: http.MethodGet, Path: "/v4/accounts", Func:
ar.ListAccount},
{Method: http.MethodGet, Path: "/v4/accounts/:name", Func:
ar.GetAccount},
{Method: http.MethodDelete, Path: "/v4/accounts/:name", Func:
ar.DeleteAccount},
@@ -218,6 +219,28 @@ func (ar *AuthResource) ListLock(w http.ResponseWriter, r
*http.Request) {
rest.WriteResponse(w, r, nil, resp)
}
+func (ar *AuthResource) BatchCreateAccount(w http.ResponseWriter, r
*http.Request) {
+ body, err := io.ReadAll(r.Body)
+ if err != nil {
+ log.Error("read body err", err)
+ rest.WriteError(w, discovery.ErrInternal, err.Error())
+ return
+ }
+ a := &rbacmodel.BatchCreateAccountsRequest{}
+ if err = json.Unmarshal(body, a); err != nil {
+ log.Error("json err", err)
+ rest.WriteError(w, discovery.ErrInvalidParams, err.Error())
+ return
+ }
+ resp, err := rbacsvc.BatchCreateAccounts(r.Context(), a)
+ if err != nil {
+ log.Error(errorsEx.MsgBatchCreateAccountsFailed, err)
+ rest.WriteServiceError(w, err)
+ return
+ }
+ rest.WriteResponse(w, r, nil, resp)
+}
+
func MakeBanKey(name, ip string) string {
return name + "::" + ip
}
diff --git a/server/resource/rbac/auth_resource_test.go
b/server/resource/rbac/auth_resource_test.go
index a0f46593..e9074e0f 100644
--- a/server/resource/rbac/auth_resource_test.go
+++ b/server/resource/rbac/auth_resource_test.go
@@ -84,6 +84,7 @@ func init() {
rest.RegisterServant(&v4.AuthResource{})
rest.RegisterServant(&v4.RoleResource{})
}
+
func TestAuthResource_Login(t *testing.T) {
ctx := context.TODO()
@@ -179,17 +180,11 @@ func TestAuthResource_Login(t *testing.T) {
})
}
+
func TestAuthResource_DeleteAccount(t *testing.T) {
- rootToken := &rbacmodel.Token{}
+ rootToken := getToken(t, "root", devPwd1)
t.Run("given root, try to delete it, should fail ", func(t *testing.T) {
- b, _ := json.Marshal(&rbacmodel.Account{Name: "root", Password:
devPwd1})
-
- r, _ := http.NewRequest(http.MethodPost, "/v4/token",
bytes.NewBuffer(b))
- w := httptest.NewRecorder()
- rest.GetRouter().ServeHTTP(w, r)
- assert.Equal(t, http.StatusOK, w.Code)
- json.Unmarshal(w.Body.Bytes(), rootToken)
r2, _ := http.NewRequest(http.MethodDelete,
"/v4/accounts/root", nil)
r2.Header.Set(restful.HeaderAuth, "Bearer "+rootToken.TokenStr)
@@ -246,18 +241,9 @@ func TestAuthResource_DeleteAccount(t *testing.T) {
assert.Equal(t, http.StatusOK, w3.Code)
})
t.Run("root can delete account", func(t *testing.T) {
- var err error
- b, err := json.Marshal(&rbacmodel.Account{Name: "root",
Password: devPwd1})
- assert.NoError(t, err)
- r, err := http.NewRequest(http.MethodPost, "/v4/token",
bytes.NewBuffer(b))
- assert.NoError(t, err)
- w := httptest.NewRecorder()
- rest.GetRouter().ServeHTTP(w, r)
- assert.Equal(t, http.StatusOK, w.Code)
- to := &rbacmodel.Token{}
- json.Unmarshal(w.Body.Bytes(), to)
+ to := getToken(t, "root", devPwd1)
- b, _ = json.Marshal(&rbacmodel.Account{Name: "delete_account",
Password: devPwd1, Roles: []string{rbacmodel.RoleDeveloper}})
+ b, _ := json.Marshal(&rbacmodel.Account{Name: "delete_account",
Password: devPwd1, Roles: []string{rbacmodel.RoleDeveloper}})
r2, _ := http.NewRequest(http.MethodPost, "/v4/accounts",
bytes.NewBuffer(b))
r2.Header.Set(restful.HeaderAuth, "Bearer "+to.TokenStr)
w2 := httptest.NewRecorder()
@@ -271,15 +257,10 @@ func TestAuthResource_DeleteAccount(t *testing.T) {
assert.Equal(t, http.StatusOK, w3.Code)
})
}
+
func TestAuthResource_GetAccount(t *testing.T) {
t.Run("get account", func(t *testing.T) {
- b, _ := json.Marshal(&rbacmodel.Account{Name: "root", Password:
devPwd1})
- r, _ := http.NewRequest(http.MethodPost, "/v4/token",
bytes.NewBuffer(b))
- w := httptest.NewRecorder()
- rest.GetRouter().ServeHTTP(w, r)
- assert.Equal(t, http.StatusOK, w.Code)
- to := &rbacmodel.Token{}
- json.Unmarshal(w.Body.Bytes(), to)
+ to := getToken(t, "root", devPwd1)
r3, _ := http.NewRequest(http.MethodGet,
"/v4/accounts/"+devAccount, nil)
r3.Header.Set(restful.HeaderAuth, "Bearer "+to.TokenStr)
@@ -293,14 +274,9 @@ func TestAuthResource_GetAccount(t *testing.T) {
assert.Equal(t, []string{"developer"}, a.Roles)
assert.Empty(t, a.Password)
})
+
t.Run("list account", func(t *testing.T) {
- b, _ := json.Marshal(&rbacmodel.Account{Name: "root", Password:
devPwd1})
- r, _ := http.NewRequest(http.MethodPost, "/v4/token",
bytes.NewBuffer(b))
- w := httptest.NewRecorder()
- rest.GetRouter().ServeHTTP(w, r)
- assert.Equal(t, http.StatusOK, w.Code)
- to := &rbacmodel.Token{}
- json.Unmarshal(w.Body.Bytes(), to)
+ to := getToken(t, "root", devPwd1)
r3, _ := http.NewRequest(http.MethodGet, "/v4/accounts", nil)
r3.Header.Set(restful.HeaderAuth, "Bearer "+to.TokenStr)
@@ -429,13 +405,7 @@ func TestAuthResource_SelfPerms(t *testing.T) {
func TestAuthResource_ListLock(t *testing.T) {
t.Run("admin list account, should pass", func(t *testing.T) {
- b, _ := json.Marshal(&rbacmodel.Account{Name: "root", Password:
devPwd1})
- r, _ := http.NewRequest(http.MethodPost, "/v4/token",
bytes.NewBuffer(b))
- w := httptest.NewRecorder()
- rest.GetRouter().ServeHTTP(w, r)
- assert.Equal(t, http.StatusOK, w.Code)
- to := &rbacmodel.Token{}
- json.Unmarshal(w.Body.Bytes(), to)
+ to := getToken(t, "root", devPwd1)
r3, _ := http.NewRequest(http.MethodGet, "/v4/account-locks",
nil)
r3.Header.Set(restful.HeaderAuth, "Bearer "+to.TokenStr)
@@ -443,7 +413,7 @@ func TestAuthResource_ListLock(t *testing.T) {
rest.GetRouter().ServeHTTP(w3, r3)
assert.Equal(t, http.StatusOK, w3.Code)
resp := &rbac.LockResponse{}
- err := json.Unmarshal(w.Body.Bytes(), resp)
+ err := json.Unmarshal(w3.Body.Bytes(), resp)
assert.NoError(t, err)
})
t.Run("not admin list account, should return 403", func(t *testing.T) {
@@ -451,13 +421,9 @@ func TestAuthResource_ListLock(t *testing.T) {
err := rbacsvc.CreateAccount(context.TODO(),
&rbacmodel.Account{Name: testListLock, Password: devPwd1, Roles:
[]string{"developer"}})
assert.NoError(t, err)
- b, _ := json.Marshal(&rbacmodel.Account{Name: testListLock,
Password: devPwd1})
- r, _ := http.NewRequest(http.MethodPost, "/v4/token",
bytes.NewBuffer(b))
- w := httptest.NewRecorder()
- rest.GetRouter().ServeHTTP(w, r)
- assert.Equal(t, http.StatusOK, w.Code)
- to := &rbacmodel.Token{}
- json.Unmarshal(w.Body.Bytes(), to)
+ defer rbacsvc.DeleteAccount(context.TODO(), testListLock)
+
+ to := getToken(t, testListLock, devPwd1)
r3, _ := http.NewRequest(http.MethodGet, "/v4/account-locks",
nil)
r3.Header.Set(restful.HeaderAuth, "Bearer "+to.TokenStr)
@@ -467,6 +433,39 @@ func TestAuthResource_ListLock(t *testing.T) {
})
}
+func TestAuthResource_BatchCreateAccount(t *testing.T) {
+ to := getToken(t, "root", devPwd1)
+
+ b, _ := json.Marshal(&rbacmodel.BatchCreateAccountsRequest{Accounts:
[]*rbacmodel.Account{
+ {Name: "TestAuthResource_BatchCreateAccount_1", Password:
devPwd1, Roles: []string{"developer"}},
+ }})
+ r, _ := http.NewRequest(http.MethodPost, "/v4/accounts/batch-create",
bytes.NewBuffer(b))
+ r.Header.Set(restful.HeaderAuth, "Bearer "+to.TokenStr)
+ w := httptest.NewRecorder()
+ rest.GetRouter().ServeHTTP(w, r)
+ assert.Equal(t, http.StatusOK, w.Code)
+
+ defer rbacsvc.DeleteAccount(context.TODO(),
"TestAuthResource_BatchCreateAccount_1")
+
+ a := &rbacmodel.BatchCreateAccountsResponse{}
+ json.Unmarshal(w.Body.Bytes(), a)
+
+ item := a.Accounts[0]
+ assert.Empty(t, item.Error)
+ assert.Equal(t, "TestAuthResource_BatchCreateAccount_1", item.Name)
+}
+
+func getToken(t *testing.T, name, pwd string) *rbacmodel.Token {
+ b, _ := json.Marshal(&rbacmodel.Account{Name: name, Password: pwd})
+ r, _ := http.NewRequest(http.MethodPost, "/v4/token",
bytes.NewBuffer(b))
+ w := httptest.NewRecorder()
+ rest.GetRouter().ServeHTTP(w, r)
+ assert.Equal(t, http.StatusOK, w.Code)
+ to := &rbacmodel.Token{}
+ json.Unmarshal(w.Body.Bytes(), to)
+ return to
+}
+
func BenchmarkAuthResource_LoginP(b *testing.B) {
body, _ := json.Marshal(&rbacmodel.Account{Name: "root", Password:
devPwd1})
b.RunParallel(func(pb *testing.PB) {
diff --git a/server/service/rbac/account_service.go
b/server/service/rbac/account_service.go
index 2dd87abc..264405ef 100644
--- a/server/service/rbac/account_service.go
+++ b/server/service/rbac/account_service.go
@@ -33,6 +33,7 @@ import (
"github.com/apache/servicecomb-service-center/server/service/validator"
"github.com/go-chassis/cari/discovery"
"github.com/go-chassis/cari/dlock"
+ "github.com/go-chassis/cari/pkg/errsvc"
rbacmodel "github.com/go-chassis/cari/rbac"
)
@@ -217,3 +218,30 @@ func AccountUsage(ctx context.Context) (int64, error) {
}
return used, nil
}
+
+func BatchCreateAccounts(ctx context.Context, req
*rbacmodel.BatchCreateAccountsRequest) (*rbacmodel.BatchCreateAccountsResponse,
error) {
+ err := validator.ValidateBatchCreateAccountsRequest(req)
+ if err != nil {
+ log.Error("batch create accounts failed", err)
+ return nil, discovery.NewError(discovery.ErrInvalidParams,
err.Error())
+ }
+
+ var resp rbacmodel.BatchCreateAccountsResponse
+ var failed int
+ for _, account := range req.Accounts {
+ err := CreateAccount(ctx, account)
+ errEx, ok := err.(*errsvc.Error)
+ if err != nil && !ok {
+ errEx = discovery.NewError(discovery.ErrInternal,
err.Error())
+ }
+ if errEx != nil {
+ failed++
+ }
+ resp.Accounts = append(resp.Accounts,
&rbacmodel.BatchCreateAccountItemResponse{
+ Name: account.Name,
+ Error: errEx,
+ })
+ }
+ log.Info(fmt.Sprintf("batch create accounts finish, succeed: %d,
failed: %d", len(resp.Accounts)-failed, failed))
+ return &resp, nil
+}
diff --git a/server/service/rbac/account_service_test.go
b/server/service/rbac/account_service_test.go
index a30e2b5c..0ccc6354 100644
--- a/server/service/rbac/account_service_test.go
+++ b/server/service/rbac/account_service_test.go
@@ -219,6 +219,7 @@ func TestGetAccount(t *testing.T) {
assert.Equal(t, rbac.ErrAccountNotExist, svcErr.Code)
})
}
+
func TestListAccount(t *testing.T) {
t.Run("list account, should succeed", func(t *testing.T) {
accounts, n, err := rbacsvc.ListAccount(context.TODO())
@@ -227,3 +228,40 @@ func TestListAccount(t *testing.T) {
assert.Equal(t, n, int64(len(accounts)))
})
}
+
+func TestBatchCreateAccounts(t *testing.T) {
+ ctx := context.TODO()
+
+ t.Run("batch create invalid accounts, should failed", func(t
*testing.T) {
+ resp, err := rbacsvc.BatchCreateAccounts(ctx,
&rbac.BatchCreateAccountsRequest{})
+ assert.Nil(t, resp)
+ assert.Error(t, err)
+ svcErr := err.(*errsvc.Error)
+ assert.Equal(t, discovery.ErrInvalidParams, svcErr.Code)
+ })
+ t.Run("batch create accounts, should succeed", func(t *testing.T) {
+ a1 := newAccount("TestBatchCreateAccounts_account_1")
+ a2 := newAccount("TestBatchCreateAccounts_account_no_pwd")
+ a2.Password = ""
+
+ defer func() {
+ rbacsvc.DeleteAccount(ctx,
"TestBatchCreateAccounts_account_1")
+ rbacsvc.DeleteAccount(ctx,
"TestBatchCreateAccounts_account_no_pwd")
+ }()
+
+ resp, err := rbacsvc.BatchCreateAccounts(ctx,
&rbac.BatchCreateAccountsRequest{
+ Accounts: []*rbac.Account{a1, a2},
+ })
+ assert.NotNil(t, resp)
+ assert.NoError(t, err)
+ assert.Equal(t, 2, len(resp.Accounts))
+
+ item := resp.Accounts[0]
+ assert.Equal(t, "TestBatchCreateAccounts_account_1", item.Name)
+ assert.Nil(t, item.Error)
+
+ item = resp.Accounts[1]
+ assert.Equal(t, "TestBatchCreateAccounts_account_no_pwd",
item.Name)
+ assert.NotEmpty(t, item.Code)
+ })
+}
diff --git a/server/service/validator/rbac_validator.go
b/server/service/validator/rbac_validator.go
index 425beb4d..acb18706 100644
--- a/server/service/validator/rbac_validator.go
+++ b/server/service/validator/rbac_validator.go
@@ -25,7 +25,7 @@ import (
var createAccountValidator = &validate.Validator{}
var updateAccountValidator = &validate.Validator{}
var createRoleValidator = &validate.Validator{}
-
+var batchCreateAccountsRequestValidator = &validate.Validator{}
var changePWDValidator = &validate.Validator{}
var accountLoginValidator = &validate.Validator{}
@@ -35,6 +35,8 @@ func init() {
createAccountValidator.AddRule("Password", &validate.Rule{Regexp:
&validate.PasswordChecker{}})
createAccountValidator.AddRule("Status", &validate.Rule{Regexp:
accountStatusRegex})
+ batchCreateAccountsRequestValidator.AddRule("Accounts",
&validate.Rule{Min: 1, Max: 20})
+
updateAccountValidator.AddRule("Roles",
createAccountValidator.GetRule("Roles"))
updateAccountValidator.AddRule("Status",
createAccountValidator.GetRule("Status"))
@@ -53,7 +55,13 @@ func ValidateCreateAccount(a *rbac.Account) error {
}
return createAccountValidator.Validate(a)
}
-
+func ValidateBatchCreateAccountsRequest(a *rbac.BatchCreateAccountsRequest)
error {
+ err := baseCheck(a)
+ if err != nil {
+ return err
+ }
+ return batchCreateAccountsRequestValidator.Validate(a)
+}
func ValidateUpdateAccount(a *rbac.Account) error {
err := baseCheck(a)
if err != nil {