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 2f66380 Add account/auth error code (#1029) 2f66380 is described below commit 2f66380422260d980f563a8fb1f39befb862b532 Author: humingcheng <humingcheng1...@163.com> AuthorDate: Mon May 31 19:12:03 2021 +0800 Add account/auth error code (#1029) * Add account/auth error code * Add account/auth error code --- datasource/etcd/account.go | 3 + datasource/mongo/account.go | 6 +- pkg/rest/util.go | 5 + server/handler/auth/auth.go | 5 + server/plugin/auth/buildin/buidlin_test.go | 25 ++- server/plugin/auth/buildin/buildin.go | 9 +- server/resource/v4/auth_resource.go | 65 +++---- server/resource/v4/auth_resource_test.go | 15 +- server/resource/v4/role_resource.go | 12 +- server/resource/v4/role_resource_test.go | 98 +++++++---- server/service/rbac/account_dao.go | 179 +++++++++++++++++++ server/service/rbac/account_dao_test.go | 228 +++++++++++++++++++++++++ server/service/rbac/authr_plugin.go | 25 ++- server/service/rbac/dao/account_dao.go | 109 ------------ server/service/rbac/dao/account_dao_test.go | 82 --------- server/service/rbac/password.go | 7 +- server/service/rbac/rbac.go | 27 ++- server/service/rbac/rbac_test.go | 32 +--- server/service/rbac/role.go | 3 +- server/service/rbac/{dao => }/role_dao.go | 2 +- server/service/rbac/{dao => }/role_dao_test.go | 53 +++--- 21 files changed, 614 insertions(+), 376 deletions(-) diff --git a/datasource/etcd/account.go b/datasource/etcd/account.go index bcf37e4..487adea 100644 --- a/datasource/etcd/account.go +++ b/datasource/etcd/account.go @@ -115,6 +115,9 @@ func (ds *DataSource) GetAccount(ctx context.Context, name string) (*rbac.Accoun if err != nil { return nil, err } + if resp.Count == 0 { + return nil, datasource.ErrAccountNotExist + } if resp.Count != 1 { return nil, client.ErrNotUnique } diff --git a/datasource/mongo/account.go b/datasource/mongo/account.go index effd9e6..0d297cc 100644 --- a/datasource/mongo/account.go +++ b/datasource/mongo/account.go @@ -20,6 +20,7 @@ package mongo import ( "context" "fmt" + "go.mongodb.org/mongo-driver/mongo" "github.com/go-chassis/cari/rbac" @@ -80,7 +81,10 @@ func (ds *DataSource) GetAccount(ctx context.Context, name string) (*rbac.Accoun log.Error(msg, err) return nil, err } - if result.Err() != nil { + if err = result.Err(); err != nil { + if err == mongo.ErrNoDocuments { + return nil, datasource.ErrAccountNotExist + } msg := fmt.Sprintf("failed to query account, account name %s", name) log.Error(msg, result.Err()) return nil, datasource.ErrQueryAccountFailed diff --git a/pkg/rest/util.go b/pkg/rest/util.go index d707b55..142ef12 100644 --- a/pkg/rest/util.go +++ b/pkg/rest/util.go @@ -22,6 +22,7 @@ import ( "encoding/json" "errors" "io/ioutil" + "github.com/go-chassis/cari/pkg/errsvc" "net/http" "github.com/apache/servicecomb-service-center/pkg/log" @@ -33,6 +34,10 @@ var errNilRequestBody = errors.New("request body is nil") func WriteError(w http.ResponseWriter, code int32, detail string) { err := discovery.NewError(code, detail) + WriteErrsvcError(w, err) +} + +func WriteErrsvcError(w http.ResponseWriter, err *errsvc.Error) { w.Header().Set(HeaderContentType, ContentTypeJSON) w.WriteHeader(err.StatusCode()) b, _ := json.Marshal(err) diff --git a/server/handler/auth/auth.go b/server/handler/auth/auth.go index 641124b..9e693e4 100644 --- a/server/handler/auth/auth.go +++ b/server/handler/auth/auth.go @@ -18,6 +18,7 @@ package auth import ( + "github.com/go-chassis/cari/pkg/errsvc" "net/http" "github.com/apache/servicecomb-service-center/pkg/chain" @@ -45,6 +46,10 @@ func (h *Handler) Handle(i *chain.Invocation) { if err := auth.Identify(r); err != nil { log.Errorf(err, "authenticate request failed, %s %s", r.Method, r.RequestURI) + if e, ok := err.(*errsvc.Error); ok { + i.Fail(e) + return + } i.Fail(discovery.NewError(rbac.ErrUnauthorized, err.Error())) return } diff --git a/server/plugin/auth/buildin/buidlin_test.go b/server/plugin/auth/buildin/buidlin_test.go index f337682..17b7354 100644 --- a/server/plugin/auth/buildin/buidlin_test.go +++ b/server/plugin/auth/buildin/buidlin_test.go @@ -22,7 +22,8 @@ import ( "context" "github.com/apache/servicecomb-service-center/pkg/rest" "github.com/apache/servicecomb-service-center/pkg/util" - rbacmodel "github.com/go-chassis/cari/rbac" + "github.com/go-chassis/cari/pkg/errsvc" + "github.com/go-chassis/cari/rbac" "io/ioutil" "net/http" "net/http/httptest" @@ -30,8 +31,7 @@ import ( "github.com/apache/servicecomb-service-center/server/config" "github.com/apache/servicecomb-service-center/server/plugin/auth/buildin" - "github.com/apache/servicecomb-service-center/server/service/rbac" - "github.com/apache/servicecomb-service-center/server/service/rbac/dao" + rbacsvc "github.com/apache/servicecomb-service-center/server/service/rbac" _ "github.com/apache/servicecomb-service-center/test" "github.com/astaxie/beego" carirbac "github.com/go-chassis/cari/rbac" @@ -51,8 +51,8 @@ func init() { } func TestTokenAuthenticator_Identify(t *testing.T) { - dao.DeleteAccount(context.TODO(), "root") - dao.DeleteAccount(context.TODO(), "non-admin") + rbacsvc.DeleteAccount(context.TODO(), "root") + rbacsvc.DeleteAccount(context.TODO(), "non-admin") t.Run("init rbac", func(t *testing.T) { err := archaius.Init(archaius.WithMemorySource(), archaius.WithENVSource()) assert.NoError(t, err) @@ -67,9 +67,9 @@ func TestTokenAuthenticator_Identify(t *testing.T) { err = ioutil.WriteFile("./rbac.pub", b, 0600) assert.NoError(t, err) - archaius.Set(rbac.InitPassword, "Complicated_password1") + archaius.Set(rbacsvc.InitPassword, "Complicated_password1") - rbac.Init() + rbacsvc.Init() }) a := buildin.New() ta := a.(*buildin.TokenAuthenticator) @@ -77,8 +77,10 @@ func TestTokenAuthenticator_Identify(t *testing.T) { t.Run("without auth header should failed", func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/any", nil) err := ta.Identify(r) + assert.NotNil(t, err) t.Log(err) - assert.Equal(t, carirbac.ErrNoHeader, err) + svcErr := err.(*errsvc.Error) + assert.Equal(t, carirbac.ErrNoAuthHeader, svcErr.Code) }) t.Run("with wrong auth header should failed", func(t *testing.T) { @@ -105,7 +107,12 @@ func TestTokenAuthenticator_Identify(t *testing.T) { assert.NoError(t, err) }) t.Run("valid normal token, should no be able to get account", func(t *testing.T) { - err := dao.CreateAccount(context.TODO(), &rbacmodel.Account{Name: "non-admin", Password: "Complicated_password1"}) + a := &rbac.Account{ + Name: "non-admin", + Password: "Complicated_password1", + Roles: []string{rbac.RoleDeveloper}, + } + err := rbacsvc.CreateAccount(context.TODO(), a) assert.NoError(t, err) r := httptest.NewRequest(http.MethodGet, "/v4/accounts", nil) to, err := authr.Login(context.TODO(), "non-admin", "Complicated_password1") diff --git a/server/plugin/auth/buildin/buildin.go b/server/plugin/auth/buildin/buildin.go index 1b19d33..ff00102 100644 --- a/server/plugin/auth/buildin/buildin.go +++ b/server/plugin/auth/buildin/buildin.go @@ -22,7 +22,6 @@ import ( "net/http" "strings" - errorsEx "github.com/apache/servicecomb-service-center/pkg/errors" "github.com/apache/servicecomb-service-center/pkg/log" "github.com/apache/servicecomb-service-center/pkg/plugin" "github.com/apache/servicecomb-service-center/pkg/rest" @@ -73,7 +72,7 @@ func (ba *TokenAuthenticator) Identify(req *http.Request) error { } account, err := rbac.GetAccount(m) if err != nil { - log.Error("get account failed", err) + log.Error("get account from token failed", err) return err } util.SetRequestContext(req, rbacsvc.CtxRequestClaims, m) @@ -84,7 +83,7 @@ func (ba *TokenAuthenticator) Identify(req *http.Request) error { if len(account.Roles) == 0 { log.Error("no role found in token", nil) - return errors.New(errorsEx.MsgNoPerm) + return errors.New("no role found in token") } project := req.URL.Query().Get(":project") @@ -93,7 +92,7 @@ func (ba *TokenAuthenticator) Identify(req *http.Request) error { return err } if !allow { - return errors.New(errorsEx.MsgNoPerm) + return rbac.NewError(rbac.ErrNoPermission, "") } util.SetRequestContext(req, authHandler.CtxResourceLabels, matchedLabels) @@ -123,7 +122,7 @@ func filterRoles(roleList []string) (hasAdmin bool, normalRoles []string) { func (ba *TokenAuthenticator) VerifyToken(req *http.Request) (interface{}, error) { v := req.Header.Get(restful.HeaderAuth) if v == "" { - return nil, rbac.ErrNoHeader + return nil, rbac.NewError(rbac.ErrNoAuthHeader, "") } s := strings.Split(v, " ") if len(s) != 2 { diff --git a/server/resource/v4/auth_resource.go b/server/resource/v4/auth_resource.go index 26c4372..1c92440 100644 --- a/server/resource/v4/auth_resource.go +++ b/server/resource/v4/auth_resource.go @@ -20,16 +20,15 @@ package v4 import ( "encoding/json" "fmt" + "github.com/go-chassis/cari/pkg/errsvc" "io/ioutil" "net/http" - "github.com/apache/servicecomb-service-center/datasource" errorsEx "github.com/apache/servicecomb-service-center/pkg/errors" "github.com/apache/servicecomb-service-center/pkg/log" "github.com/apache/servicecomb-service-center/pkg/rest" "github.com/apache/servicecomb-service-center/pkg/util" rbacsvc "github.com/apache/servicecomb-service-center/server/service/rbac" - "github.com/apache/servicecomb-service-center/server/service/rbac/dao" "github.com/apache/servicecomb-service-center/server/service/validator" "github.com/go-chassis/cari/discovery" @@ -63,27 +62,13 @@ func (ar *AuthResource) CreateAccount(w http.ResponseWriter, req *http.Request) a := &rbac.Account{} if err = json.Unmarshal(body, a); err != nil { log.Error("json err", err) - rest.WriteError(w, discovery.ErrInvalidParams, errorsEx.MsgJSON) - return - } - err = validator.ValidateCreateAccount(a) - if err != nil { - rest.WriteError(w, discovery.ErrInvalidParams, err.Error()) - return - } - err = a.Check() - if err != nil { rest.WriteError(w, discovery.ErrInvalidParams, err.Error()) return } - err = dao.CreateAccount(req.Context(), a) + err = rbacsvc.CreateAccount(req.Context(), a) if err != nil { - if err == datasource.ErrAccountDuplicated { - rest.WriteError(w, rbac.ErrAccountConflict, "") - return - } log.Error(errorsEx.MsgOperateAccountFailed, err) - rest.WriteError(w, discovery.ErrInternal, errorsEx.MsgOperateAccountFailed) + writeErrsvcOrInternalErr(w, err) return } rest.WriteSuccess(w, req) @@ -91,13 +76,10 @@ func (ar *AuthResource) CreateAccount(w http.ResponseWriter, req *http.Request) func (ar *AuthResource) DeleteAccount(w http.ResponseWriter, req *http.Request) { name := req.URL.Query().Get(":name") - if ar.illegalCheck(w, req, name) { - return - } - _, err := dao.DeleteAccount(req.Context(), name) + err := rbacsvc.DeleteAccount(req.Context(), name) if err != nil { log.Error(errorsEx.MsgOperateAccountFailed, err) - rest.WriteError(w, discovery.ErrInternal, errorsEx.MsgOperateAccountFailed) + writeErrsvcOrInternalErr(w, err) return } rest.WriteSuccess(w, req) @@ -105,9 +87,6 @@ func (ar *AuthResource) DeleteAccount(w http.ResponseWriter, req *http.Request) func (ar *AuthResource) UpdateAccount(w http.ResponseWriter, req *http.Request) { name := req.URL.Query().Get(":name") - if ar.illegalCheck(w, req, name) { - return - } body, err := ioutil.ReadAll(req.Body) if err != nil { log.Error("read body err", err) @@ -121,17 +100,17 @@ func (ar *AuthResource) UpdateAccount(w http.ResponseWriter, req *http.Request) return } - err = dao.UpdateAccount(req.Context(), name, a) + err = rbacsvc.UpdateAccount(req.Context(), name, a) if err != nil { log.Error(errorsEx.MsgOperateAccountFailed, err) - rest.WriteError(w, discovery.ErrInternal, errorsEx.MsgOperateAccountFailed) + writeErrsvcOrInternalErr(w, err) return } rest.WriteSuccess(w, req) } func (ar *AuthResource) ListAccount(w http.ResponseWriter, r *http.Request) { - as, n, err := dao.ListAccount(r.Context()) + as, n, err := rbacsvc.ListAccount(r.Context()) if err != nil { log.Error(errorsEx.MsgGetAccountFailed, err) rest.WriteError(w, discovery.ErrInternal, errorsEx.MsgGetAccountFailed) @@ -145,29 +124,16 @@ func (ar *AuthResource) ListAccount(w http.ResponseWriter, r *http.Request) { } func (ar *AuthResource) GetAccount(w http.ResponseWriter, r *http.Request) { - a, err := dao.GetAccount(r.Context(), r.URL.Query().Get(":name")) + a, err := rbacsvc.GetAccount(r.Context(), r.URL.Query().Get(":name")) if err != nil { log.Error(errorsEx.MsgGetAccountFailed, err) - rest.WriteError(w, discovery.ErrInternal, errorsEx.MsgGetAccountFailed) + writeErrsvcOrInternalErr(w, err) return } a.Password = "" rest.WriteResponse(w, r, nil, a) } -func (ar *AuthResource) illegalCheck(w http.ResponseWriter, req *http.Request, name string) bool { - if name == rbacsvc.RootName { - rest.WriteError(w, discovery.ErrInvalidParams, errorsEx.MsgCantOperateRoot) - return true - } - user := rbacsvc.UserFromContext(req.Context()) - if name == user { - rest.WriteError(w, discovery.ErrInvalidParams, errorsEx.MsgCantOperateYour) - return true - } - return false -} - func (ar *AuthResource) ChangePassword(w http.ResponseWriter, req *http.Request) { name := req.URL.Query().Get(":name") ip := util.GetRealIP(req) @@ -253,7 +219,7 @@ func (ar *AuthResource) Login(w http.ResponseWriter, r *http.Request) { if err == rbacsvc.ErrUnauthorized { log.Error("not authorized", err) rbacsvc.CountFailure(MakeBanKey(a.Name, ip)) - rest.WriteError(w, rbac.ErrUnauthorized, err.Error()) + rest.WriteError(w, rbac.ErrUserOrPwdWrong, err.Error()) return } log.Error("can not sign token", err) @@ -266,3 +232,12 @@ func (ar *AuthResource) Login(w http.ResponseWriter, r *http.Request) { func MakeBanKey(name, ip string) string { return name + "::" + ip } + +func writeErrsvcOrInternalErr(w http.ResponseWriter, err error) { + e, ok := err.(*errsvc.Error) + if ok { + rest.WriteErrsvcError(w, e) + return + } + rest.WriteError(w, discovery.ErrInternal, err.Error()) +} diff --git a/server/resource/v4/auth_resource_test.go b/server/resource/v4/auth_resource_test.go index 6e0e233..38c1d7f 100644 --- a/server/resource/v4/auth_resource_test.go +++ b/server/resource/v4/auth_resource_test.go @@ -32,8 +32,7 @@ import ( "github.com/apache/servicecomb-service-center/pkg/rest" "github.com/apache/servicecomb-service-center/server/config" v4 "github.com/apache/servicecomb-service-center/server/resource/v4" - "github.com/apache/servicecomb-service-center/server/service/rbac" - "github.com/apache/servicecomb-service-center/server/service/rbac/dao" + rbacsvc "github.com/apache/servicecomb-service-center/server/service/rbac" _ "github.com/apache/servicecomb-service-center/test" "github.com/astaxie/beego" "github.com/go-chassis/go-archaius" @@ -71,18 +70,18 @@ func init() { panic(err) } - archaius.Set(rbac.InitPassword, pwd) + archaius.Set(rbacsvc.InitPassword, pwd) ctx := context.TODO() - dao.DeleteAccount(ctx, "root") + rbacsvc.DeleteAccount(ctx, "root") - rbac.Init() + rbacsvc.Init() rest.RegisterServant(&v4.AuthResource{}) rest.RegisterServant(&v4.RoleResource{}) } func TestAuthResource_Login(t *testing.T) { ctx := context.TODO() - dao.DeleteAccount(ctx, "dev_account") + rbacsvc.DeleteAccount(ctx, "dev_account") t.Run("invalid user login", func(t *testing.T) { b, _ := json.Marshal(&rbacmodel.Account{Name: "dev_account", Password: pwd}) @@ -190,7 +189,7 @@ func TestAuthResource_DeleteAccount(t *testing.T) { r2.Header.Set(restful.HeaderAuth, "Bearer "+rootToken.TokenStr) w2 := httptest.NewRecorder() rest.GetRouter().ServeHTTP(w2, r2) - assert.Equal(t, http.StatusBadRequest, w2.Code) + assert.Equal(t, http.StatusForbidden, w2.Code) }) t.Run("dev_account can not even delete him self", func(t *testing.T) { b, _ := json.Marshal(&rbacmodel.Account{Name: "dev_account", Password: "Complicated_password2", Roles: []string{"developer"}}) @@ -232,7 +231,7 @@ func TestAuthResource_DeleteAccount(t *testing.T) { r2.Header.Set(restful.HeaderAuth, "Bearer "+yourToken.TokenStr) w2 := httptest.NewRecorder() rest.GetRouter().ServeHTTP(w2, r2) - assert.Equal(t, http.StatusBadRequest, w2.Code) + assert.Equal(t, http.StatusForbidden, w2.Code) r3, _ := http.NewRequest(http.MethodDelete, "/v4/accounts/your_account", nil) r3.Header.Set(restful.HeaderAuth, "Bearer "+rootToken.TokenStr) diff --git a/server/resource/v4/role_resource.go b/server/resource/v4/role_resource.go index 17e363b..a2debe8 100644 --- a/server/resource/v4/role_resource.go +++ b/server/resource/v4/role_resource.go @@ -21,13 +21,13 @@ import ( "encoding/json" "errors" "github.com/apache/servicecomb-service-center/datasource" + rbacsvc "github.com/apache/servicecomb-service-center/server/service/rbac" "io/ioutil" "net/http" errorsEx "github.com/apache/servicecomb-service-center/pkg/errors" "github.com/apache/servicecomb-service-center/pkg/log" "github.com/apache/servicecomb-service-center/pkg/rest" - "github.com/apache/servicecomb-service-center/server/service/rbac/dao" "github.com/go-chassis/cari/discovery" "github.com/go-chassis/cari/rbac" @@ -51,7 +51,7 @@ func (rr *RoleResource) URLPatterns() []rest.Route { //ListRoles list all roles and there's permissions func (rr *RoleResource) ListRoles(w http.ResponseWriter, req *http.Request) { - rs, num, err := dao.ListRole(req.Context()) + rs, num, err := rbacsvc.ListRole(req.Context()) if err != nil { log.Error(errorsEx.MsgGetRoleFailed, err) rest.WriteError(w, discovery.ErrInternal, errorsEx.MsgGetRoleFailed) @@ -90,7 +90,7 @@ func (rr *RoleResource) CreateRole(w http.ResponseWriter, req *http.Request) { return } - status, err := dao.CreateRole(req.Context(), role) + status, err := rbacsvc.CreateRole(req.Context(), role) if err != nil { log.Error(errorsEx.MsgOperateRoleFailed, err) rest.WriteError(w, discovery.ErrInternal, err.Error()) @@ -113,7 +113,7 @@ func (rr *RoleResource) UpdateRole(w http.ResponseWriter, req *http.Request) { rest.WriteError(w, discovery.ErrInvalidParams, errorsEx.MsgJSON) return } - status, err := dao.EditRole(req.Context(), name, role) + status, err := rbacsvc.EditRole(req.Context(), name, role) if err != nil { log.Error(errorsEx.MsgOperateRoleFailed, err) rest.WriteError(w, discovery.ErrInternal, errorsEx.MsgOperateRoleFailed) @@ -125,7 +125,7 @@ func (rr *RoleResource) UpdateRole(w http.ResponseWriter, req *http.Request) { //GetRole get the role info according to role name func (rr *RoleResource) GetRole(w http.ResponseWriter, r *http.Request) { - resp, status, err := dao.GetRole(r.Context(), r.URL.Query().Get(":roleName")) + resp, status, err := rbacsvc.GetRole(r.Context(), r.URL.Query().Get(":roleName")) if err != nil { log.Error(errorsEx.MsgGetRoleFailed, err) rest.WriteError(w, discovery.ErrInternal, errorsEx.MsgGetRoleFailed) @@ -139,7 +139,7 @@ func (rr *RoleResource) GetRole(w http.ResponseWriter, r *http.Request) { func (rr *RoleResource) DeleteRole(w http.ResponseWriter, req *http.Request) { n := req.URL.Query().Get(":roleName") - status, err := dao.DeleteRole(req.Context(), n) + status, err := rbacsvc.DeleteRole(req.Context(), n) if errors.Is(err, datasource.ErrRoleBindingExist) { rest.WriteError(w, discovery.ErrInvalidParams, errorsEx.MsgJSON) return diff --git a/server/resource/v4/role_resource_test.go b/server/resource/v4/role_resource_test.go index 4598496..6ac0e36 100644 --- a/server/resource/v4/role_resource_test.go +++ b/server/resource/v4/role_resource_test.go @@ -1,14 +1,15 @@ package v4_test import ( + rbacsvc "github.com/apache/servicecomb-service-center/server/service/rbac" _ "github.com/apache/servicecomb-service-center/test" + "github.com/go-chassis/cari/rbac" "strings" "bytes" "context" "encoding/json" "github.com/apache/servicecomb-service-center/pkg/rest" - "github.com/apache/servicecomb-service-center/server/service/rbac/dao" rbacmodel "github.com/go-chassis/cari/rbac" "github.com/go-chassis/go-chassis/v2/server/restful" "github.com/stretchr/testify/assert" @@ -17,12 +18,46 @@ import ( "testing" ) +func newRole(name string) *rbac.Role { + return &rbac.Role{ + Name: name, + Perms: []*rbac.Permission{ + { + Resources: []*rbac.Resource{ + { + Type: rbacsvc.ResourceService, + }, + }, + Verbs: []string{"*"}, + }, + }, + } +} + +const ( + testPwd0 = "Ab@00000" + testPwd1 = "Ab@11111" +) + +func newAccount(name string) *rbac.Account { + return &rbac.Account{ + Name: name, + Password: testPwd0, + Roles: []string{rbac.RoleAdmin}, + Status: "active", + } +} + func TestRoleResource_CreateOrUpdateRole(t *testing.T) { var superToken = &rbacmodel.Token{} ctx := context.TODO() - dao.DeleteAccount(ctx, "dev_test") - dao.DeleteAccount(ctx, "dev_test2") - dao.DeleteRole(ctx, "tester") + rbacsvc.DeleteAccount(ctx, "dev_test") + rbacsvc.DeleteAccount(ctx, "dev_test2") + rbacsvc.DeleteRole(ctx, "tester") + devAccount := newAccount("dev_test") + testRole := newRole("tester") + rbacsvc.DeleteAccount(ctx, devAccount.Name) + rbacsvc.DeleteRole(ctx, testRole.Name) t.Run("root login,to get super token", func(t *testing.T) { b, _ := json.Marshal(&rbacmodel.Account{Name: "root", Password: "Complicated_password1"}) @@ -34,18 +69,8 @@ func TestRoleResource_CreateOrUpdateRole(t *testing.T) { 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 "+superToken.TokenStr) - w := httptest.NewRecorder() - rest.GetRouter().ServeHTTP(w, r) - assert.Equal(t, http.StatusOK, w.Code) - }) - t.Run("create a role name tester ", func(t *testing.T) { - b, _ := json.Marshal(&rbacmodel.Account{Name: "dev_test", Password: "Complicated_password3", Roles: []string{"tester"}}) + b, _ := json.Marshal(&rbacmodel.Account{Name: rbacsvc.RootName, Password: "Complicated_password1"}) r, _ := http.NewRequest(http.MethodPost, "/v4/token", bytes.NewBuffer(b)) w := httptest.NewRecorder() @@ -54,15 +79,7 @@ func TestRoleResource_CreateOrUpdateRole(t *testing.T) { devToken := &rbacmodel.Token{} json.Unmarshal(w.Body.Bytes(), devToken) - b2, _ := json.Marshal(&rbacmodel.Role{ - Name: "tester", - Perms: []*rbacmodel.Permission{ - { - Resources: []*rbacmodel.Resource{{Type: "service"}, {Type: "instance"}}, - Verbs: []string{"get", "create", "update"}, - }, - }, - }) + b2, _ := json.Marshal(testRole) r2, _ := http.NewRequest(http.MethodPost, "/v4/roles", bytes.NewReader(b2)) r2.Header.Set(restful.HeaderAuth, "Bearer "+superToken.TokenStr) @@ -76,21 +93,30 @@ func TestRoleResource_CreateOrUpdateRole(t *testing.T) { rest.GetRouter().ServeHTTP(w3, r3) assert.Equal(t, http.StatusOK, w3.Code) - b4, _ := json.Marshal(&rbacmodel.Role{ - Name: "tester", - Perms: []*rbacmodel.Permission{ - { - Resources: []*rbacmodel.Resource{{Type: "service"}}, - Verbs: []string{"get", "create", "update"}, - }, + newTestRole := newRole(testRole.Name) + newTestRole.Perms = []*rbac.Permission{ + { + Resources: []*rbacmodel.Resource{{Type: rbacsvc.ResourceAccount}}, + Verbs: []string{"*"}, }, - }) + } + b4, _ := json.Marshal(newTestRole) r4, _ := http.NewRequest(http.MethodPut, "/v4/roles/tester", bytes.NewReader(b4)) 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("create account dev_test and add a role", func(t *testing.T) { + devAccount.Roles = []string{testRole.Name} + b, _ := json.Marshal(devAccount) + + r, _ := http.NewRequest(http.MethodPost, "/v4/accounts", bytes.NewBuffer(b)) + r.Header.Set(restful.HeaderAuth, "Bearer "+superToken.TokenStr) + w := httptest.NewRecorder() + rest.GetRouter().ServeHTTP(w, r) + assert.Equal(t, http.StatusOK, w.Code) + }) t.Run("get role", func(t *testing.T) { b, _ := json.Marshal(&rbacmodel.Account{Name: "root", Password: "Complicated_password1"}) @@ -150,10 +176,10 @@ func TestRoleResource_CreateOrUpdateRole(t *testing.T) { func TestRoleResource_MoreRoles(t *testing.T) { var to = &rbacmodel.Token{} ctx := context.TODO() - dao.DeleteAccount(ctx, "dev_test") - dao.DeleteAccount(ctx, "dev_test2") - dao.DeleteRole(ctx, "tester") - dao.DeleteRole(ctx, "tester2") + rbacsvc.DeleteAccount(ctx, "dev_test") + rbacsvc.DeleteAccount(ctx, "dev_test2") + rbacsvc.DeleteRole(ctx, "tester") + rbacsvc.DeleteRole(ctx, "tester2") t.Run("root login", func(t *testing.T) { b, _ := json.Marshal(&rbacmodel.Account{Name: "root", Password: "Complicated_password1"}) diff --git a/server/service/rbac/account_dao.go b/server/service/rbac/account_dao.go new file mode 100644 index 0000000..73ae9d1 --- /dev/null +++ b/server/service/rbac/account_dao.go @@ -0,0 +1,179 @@ +/* + * 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 is dao layer API to help service center manage account, policy and role info +package rbac + +import ( + "context" + "fmt" + "github.com/apache/servicecomb-service-center/datasource" + errorsEx "github.com/apache/servicecomb-service-center/pkg/errors" + "github.com/apache/servicecomb-service-center/pkg/log" + "github.com/apache/servicecomb-service-center/pkg/util" + "github.com/apache/servicecomb-service-center/server/plugin/quota" + "github.com/apache/servicecomb-service-center/server/service/validator" + "github.com/go-chassis/cari/discovery" + "github.com/go-chassis/cari/rbac" +) + +//CreateAccount save 2 kv +//1. account info +func CreateAccount(ctx context.Context, a *rbac.Account) error { + quotaCheckErr := quota.Apply(ctx, quota.NewApplyQuotaResource(quota.TypeAccount, + util.ParseDomainProject(ctx), "", 1)) + if quotaCheckErr != nil { + return quotaCheckErr + } + err := validator.ValidateCreateAccount(a) + if err != nil { + log.Errorf(err, "create account [%s] failed", a.Name) + return rbac.NewError(discovery.ErrInvalidParams, err.Error()) + } + err = a.Check() + if err != nil { + log.Errorf(err, "create account [%s] failed", a.Name) + return rbac.NewError(discovery.ErrInvalidParams, err.Error()) + } + if err = checkRoleNames(ctx, a.Roles); err != nil { + return rbac.NewError(rbac.ErrAccountHasInvalidRole, err.Error()) + } + + err = datasource.Instance().CreateAccount(ctx, a) + if err == nil { + log.Infof("create account [%s] success", a.Name) + return nil + } + log.Errorf(err, "create account [%s] failed", a.Name) + if err == datasource.ErrAccountDuplicated { + return rbac.NewError(rbac.ErrAccountConflict, err.Error()) + } + return err +} + +// UpdateAccount updates an account's info, except the password +func UpdateAccount(ctx context.Context, name string, a *rbac.Account) error { + // todo params validation + if err := illegalCheck(ctx, name); err != nil { + return err + } + if len(a.Status) == 0 && len(a.Roles) == 0 { + return rbac.NewError(discovery.ErrInvalidParams, "status and roles cannot be empty both") + } + + oldAccount, err := GetAccount(ctx, name) + if err != nil { + log.Errorf(err, "get account [%s] failed", name) + return err + } + if len(a.Status) != 0 { + oldAccount.Status = a.Status + } + if len(a.Roles) != 0 { + oldAccount.Roles = a.Roles + } + if err = checkRoleNames(ctx, oldAccount.Roles); err != nil { + return rbac.NewError(rbac.ErrAccountHasInvalidRole, err.Error()) + } + err = datasource.Instance().UpdateAccount(ctx, name, oldAccount) + if err != nil { + log.Errorf(err, "can not edit account info") + return err + } + log.Infof("account [%s] is edit", oldAccount.ID) + return nil +} + +func GetAccount(ctx context.Context, name string) (*rbac.Account, error) { + r, err := datasource.Instance().GetAccount(ctx, name) + if err != nil { + if err == datasource.ErrAccountNotExist { + msg := fmt.Sprintf("account [%s] not exist", name) + return nil, rbac.NewError(rbac.ErrAccountNotExist, msg) + } + return nil, err + } + return r, nil +} +func ListAccount(ctx context.Context) ([]*rbac.Account, int64, error) { + return datasource.Instance().ListAccount(ctx) +} +func AccountExist(ctx context.Context, name string) (bool, error) { + return datasource.Instance().AccountExist(ctx, name) +} +func DeleteAccount(ctx context.Context, name string) error { + if err := illegalCheck(ctx, name); err != nil { + return err + } + exist, err := datasource.Instance().AccountExist(ctx, name) + if err != nil { + log.Errorf(err, "check account [%s] exit failed", name) + return err + } + if !exist { + msg := fmt.Sprintf("account [%s] not exist", name) + return rbac.NewError(rbac.ErrAccountNotExist, msg) + } + _, err = datasource.Instance().DeleteAccount(ctx, []string{name}) + return err +} + +//CreateAccount save 2 kv +//1. account info +func EditAccount(ctx context.Context, a *rbac.Account) error { + exist, err := datasource.Instance().AccountExist(ctx, a.Name) + if err != nil { + log.Errorf(err, "can not edit account info") + return err + } + if !exist { + return rbac.NewError(rbac.ErrAccountNotExist, "") + } + + err = datasource.Instance().UpdateAccount(ctx, a.Name, a) + if err != nil { + log.Errorf(err, "can not edit account info") + return err + } + log.Infof("account [%s] is edit", a.ID) + return nil +} + +func checkRoleNames(ctx context.Context, roles []string) error { + for _, name := range roles { + exist, err := RoleExist(ctx, name) + if err != nil { + log.Errorf(err, "check role [%s] exist failed", name) + return err + } + if !exist { + return datasource.ErrRoleNotExist + } + } + return nil +} + +func illegalCheck(ctx context.Context, target string) error { + if target == RootName { + return discovery.NewError(discovery.ErrForbidden, errorsEx.MsgCantOperateRoot) + } + changer := UserFromContext(ctx) + if target == changer { + return discovery.NewError(discovery.ErrForbidden, errorsEx.MsgCantOperateYour) + } + return nil +} diff --git a/server/service/rbac/account_dao_test.go b/server/service/rbac/account_dao_test.go new file mode 100644 index 0000000..546c307 --- /dev/null +++ b/server/service/rbac/account_dao_test.go @@ -0,0 +1,228 @@ +/* + * 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_test + +// initialize +import ( + "context" + rbacsvc "github.com/apache/servicecomb-service-center/server/service/rbac" + "github.com/go-chassis/cari/discovery" + "github.com/go-chassis/cari/pkg/errsvc" + "testing" + + _ "github.com/apache/servicecomb-service-center/test" + + "github.com/astaxie/beego" + "github.com/go-chassis/cari/rbac" + "github.com/stretchr/testify/assert" +) + +func init() { + beego.AppConfig.Set("registry_plugin", "etcd") +} + +const ( + testPwd0 = "Ab@00000" + testPwd1 = "Ab@11111" +) + +func newAccount(name string) *rbac.Account { + return &rbac.Account{ + Name: name, + Password: testPwd0, + Roles: []string{rbac.RoleAdmin}, + Status: "active", + } +} + +func TestCreateAccount(t *testing.T) { + t.Run("create account, should succeed", func(t *testing.T) { + a := newAccount("TestCreateAccount_create_account") + err := rbacsvc.CreateAccount(context.TODO(), a) + assert.Nil(t, err) + }) + t.Run("create account twice, should return: "+rbac.NewError(rbac.ErrAccountConflict, "").Error(), func(t *testing.T) { + name := "TestCreateAccount_create_account_twice" + a := newAccount(name) + err := rbacsvc.CreateAccount(context.TODO(), a) + assert.Nil(t, err) + + a = newAccount(name) + err = rbacsvc.CreateAccount(context.TODO(), a) + assert.NotNil(t, err) + svcErr := err.(*errsvc.Error) + assert.Equal(t, rbac.ErrAccountConflict, svcErr.Code) + }) + t.Run("account has invalid role, should return: "+rbac.NewError(rbac.ErrAccountHasInvalidRole, "").Error(), func(t *testing.T) { + a := newAccount("TestCreateAccount_account_has_invalid_role") + a.Roles = append(a.Roles, "invalid_role") + err := rbacsvc.CreateAccount(context.TODO(), a) + assert.NotNil(t, err) + svcErr := err.(*errsvc.Error) + assert.Equal(t, rbac.ErrAccountHasInvalidRole, svcErr.Code) + }) +} + +func TestDeleteAccount(t *testing.T) { + t.Run("delete account, should succeed", func(t *testing.T) { + a := newAccount("TestDeleteAccount_delete_account") + err := rbacsvc.CreateAccount(context.TODO(), a) + assert.Nil(t, err) + + exist, err := rbacsvc.AccountExist(context.TODO(), a.Name) + assert.Nil(t, err) + assert.True(t, exist) + err = rbacsvc.DeleteAccount(context.TODO(), a.Name) + assert.Nil(t, err) + exist, err = rbacsvc.AccountExist(context.TODO(), a.Name) + assert.Nil(t, err) + assert.False(t, exist) + }) + t.Run("delete no exist account, should return: "+rbac.NewError(rbac.ErrAccountNotExist, "").Error(), func(t *testing.T) { + err := rbacsvc.DeleteAccount(context.TODO(), "TestDeleteAccount_delete_no_exist_account") + assert.NotNil(t, err) + svcErr := err.(*errsvc.Error) + assert.Equal(t, rbac.ErrAccountNotExist, svcErr.Code) + }) + t.Run("delete root, should return: "+rbac.NewError(discovery.ErrForbidden, "").Error(), func(t *testing.T) { + err := rbacsvc.DeleteAccount(context.TODO(), "root") + assert.NotNil(t, err) + svcErr := err.(*errsvc.Error) + assert.Equal(t, discovery.ErrForbidden, svcErr.Code) + }) + t.Run("delete self, should return: "+rbac.NewError(discovery.ErrForbidden, "").Error(), func(t *testing.T) { + a := newAccount("TestDeleteAccount_delete_self") + err := rbacsvc.CreateAccount(context.TODO(), a) + assert.Nil(t, err) + claims := map[string]interface{}{ + rbac.ClaimsUser: a.Name, + } + ctx := context.WithValue(context.TODO(), rbacsvc.CtxRequestClaims, claims) + err = rbacsvc.DeleteAccount(ctx, a.Name) + assert.NotNil(t, err) + svcErr := err.(*errsvc.Error) + assert.Equal(t, discovery.ErrForbidden, svcErr.Code) + }) +} + +func TestUpdateAccount(t *testing.T) { + t.Run("update account, should succeed", func(t *testing.T) { + name := "TestUpdateAccount_update_account" + a := newAccount(name) + err := rbacsvc.CreateAccount(context.TODO(), a) + assert.Nil(t, err) + + a = newAccount(name) + a.Roles = []string{rbac.RoleAdmin, rbac.RoleDeveloper} + err = rbacsvc.UpdateAccount(context.TODO(), a.Name, a) + assert.Nil(t, err) + resp, err := rbacsvc.GetAccount(context.TODO(), a.Name) + assert.Nil(t, err) + assert.Equal(t, 2, len(resp.Roles)) + }) + t.Run("update no exist account, should return: "+rbac.NewError(rbac.ErrAccountNotExist, "").Error(), func(t *testing.T) { + name := "TestUpdateAccount_update_no_exist_account" + a := newAccount(name) + err := rbacsvc.UpdateAccount(context.TODO(), a.Name, a) + assert.NotNil(t, err) + svcErr := err.(*errsvc.Error) + assert.Equal(t, rbac.ErrAccountNotExist, svcErr.Code) + }) + t.Run("update root, should return: "+discovery.NewError(discovery.ErrForbidden, "").Error(), func(t *testing.T) { + a := newAccount("root") + err := rbacsvc.UpdateAccount(context.TODO(), a.Name, a) + assert.NotNil(t, err) + svcErr := err.(*errsvc.Error) + assert.Equal(t, discovery.ErrForbidden, svcErr.Code) + }) + t.Run("account has invalid role, should return: "+rbac.NewError(rbac.ErrAccountHasInvalidRole, "").Error(), func(t *testing.T) { + name := "TestUpdateAccount_account_has_invalid_role" + a := newAccount(name) + err := rbacsvc.CreateAccount(context.TODO(), a) + assert.Nil(t, err) + + a.Roles = append(a.Roles, "invalid_role") + err = rbacsvc.UpdateAccount(context.TODO(), a.Name, a) + assert.NotNil(t, err) + svcErr := err.(*errsvc.Error) + assert.Equal(t, rbac.ErrAccountHasInvalidRole, svcErr.Code) + }) + t.Run("roles status empty both, should return: "+discovery.NewError(discovery.ErrInvalidParams, "").Error(), func(t *testing.T) { + name := "TestUpdateAccount_roles_status_empty_both" + a := newAccount(name) + err := rbacsvc.CreateAccount(context.TODO(), a) + assert.Nil(t, err) + + a = newAccount(name) + a.Roles = nil + a.Status = "" + err = rbacsvc.UpdateAccount(context.TODO(), a.Name, a) + assert.NotNil(t, err) + svcErr := err.(*errsvc.Error) + assert.Equal(t, discovery.ErrInvalidParams, svcErr.Code) + }) + t.Run("update self, should return: "+rbac.NewError(discovery.ErrForbidden, "").Error(), func(t *testing.T) { + name := "TestDeleteAccount_update_self" + a := newAccount(name) + err := rbacsvc.CreateAccount(context.TODO(), a) + assert.Nil(t, err) + + a = newAccount(name) + claims := map[string]interface{}{ + rbac.ClaimsUser: a.Name, + } + ctx := context.WithValue(context.TODO(), rbacsvc.CtxRequestClaims, claims) + err = rbacsvc.UpdateAccount(ctx, a.Name, a) + assert.NotNil(t, err) + svcErr := err.(*errsvc.Error) + assert.Equal(t, discovery.ErrForbidden, svcErr.Code) + }) +} + +func TestEditAccount(t *testing.T) { + t.Run("edit no exist account, should return: "+rbac.NewError(rbac.ErrAccountNotExist, "").Error(), func(t *testing.T) { + a := newAccount("TestEditAccount_edit_no_exist_account") + err := rbacsvc.UpdateAccount(context.TODO(), a.Name, a) + assert.NotNil(t, err) + svcErr := err.(*errsvc.Error) + assert.Equal(t, rbac.ErrAccountNotExist, svcErr.Code) + }) +} + +func TestGetAccount(t *testing.T) { + t.Run("get account, should succeed", func(t *testing.T) { + a, err := rbacsvc.GetAccount(context.TODO(), "root") + assert.Nil(t, err) + assert.Equal(t, "root", a.Name) + }) + t.Run("get no exist account, should return: "+rbac.NewError(rbac.ErrAccountNotExist, "").Error(), func(t *testing.T) { + a, err := rbacsvc.GetAccount(context.TODO(), "TestGetAccount_no_exist_account") + assert.Nil(t, a) + assert.NotNil(t, err) + svcErr := err.(*errsvc.Error) + 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()) + assert.Nil(t, err) + assert.True(t, n > 0) + assert.Equal(t, n, int64(len(accounts))) + }) +} diff --git a/server/service/rbac/authr_plugin.go b/server/service/rbac/authr_plugin.go index 00008bc..b6a4615 100644 --- a/server/service/rbac/authr_plugin.go +++ b/server/service/rbac/authr_plugin.go @@ -21,6 +21,7 @@ import ( "context" "crypto/rsa" "errors" + "fmt" "github.com/apache/servicecomb-service-center/datasource" "github.com/go-chassis/cari/rbac" @@ -30,7 +31,6 @@ import ( "github.com/apache/servicecomb-service-center/pkg/log" "github.com/apache/servicecomb-service-center/pkg/privacy" - "github.com/apache/servicecomb-service-center/server/service/rbac/dao" ) var ErrUnauthorized = errors.New("wrong user name or password") @@ -49,11 +49,12 @@ func (a *EmbeddedAuthenticator) Login(ctx context.Context, user string, password for _, o := range opts { o(opt) } - account, err := dao.GetAccount(ctx, user) + account, err := GetAccount(ctx, user) if err != nil { log.Error("get account err", err) return "", err } + same := privacy.SamePassword(account.Password, password) if user == account.Name && same { secret, err := GetPrivateKey() @@ -85,6 +86,9 @@ func (a *EmbeddedAuthenticator) Authenticate(ctx context.Context, tokenStr strin } claims, err := a.authToken(tokenStr, p) if err != nil { + if a.isTokenExpiredError(err) { + return nil, rbac.NewError(rbac.ErrTokenExpired, "") + } return nil, err } accountNameI := claims[rbac.ClaimsUser] @@ -97,11 +101,26 @@ func (a *EmbeddedAuthenticator) Authenticate(ctx context.Context, tokenStr strin return nil, err } if !exist { - return nil, datasource.ErrAccountNotExist + msg := fmt.Sprintf("account [%s] is deleted", n) + return nil, rbac.NewError(rbac.ErrTokenOwnedAccountDeleted, msg) } return claims, nil } +func (a *EmbeddedAuthenticator) isTokenExpiredError(err error) bool { + if err == nil { + return false + } + vErr, ok := err.(*jwt.ValidationError) + if !ok { + return false + } + if vErr.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { + return true + } + return false +} + func (a *EmbeddedAuthenticator) authToken(tokenStr string, pub *rsa.PublicKey) (map[string]interface{}, error) { return token.Verify(tokenStr, func(claims interface{}, method token.SigningMethod) (interface{}, error) { return pub, nil diff --git a/server/service/rbac/dao/account_dao.go b/server/service/rbac/dao/account_dao.go deleted file mode 100644 index fe7279b..0000000 --- a/server/service/rbac/dao/account_dao.go +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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 is dao layer API to help service center manage account, policy and role info -package dao - -import ( - "context" - "errors" - "github.com/apache/servicecomb-service-center/datasource" - "github.com/apache/servicecomb-service-center/pkg/log" - "github.com/apache/servicecomb-service-center/pkg/util" - "github.com/apache/servicecomb-service-center/server/plugin/quota" - - rbacmodel "github.com/go-chassis/cari/rbac" -) - -//CreateAccount save 2 kv -//1. account info -func CreateAccount(ctx context.Context, a *rbacmodel.Account) error { - err := quota.Apply(ctx, quota.NewApplyQuotaResource(quota.TypeAccount, - util.ParseDomainProject(ctx), "", 1)) - if err != nil { - return err - } - return datasource.Instance().CreateAccount(ctx, a) -} - -// UpdateAccount updates an account's info, except the password -func UpdateAccount(ctx context.Context, name string, a *rbacmodel.Account) error { - // todo params validation - if len(a.Status) == 0 && len(a.Roles) == 0 { - return errors.New("status and roles cannot be empty both") - } - exist, err := datasource.Instance().AccountExist(ctx, name) - if err != nil { - log.Errorf(err, "check account [%s] exit failed", name) - return err - } - if !exist { - return datasource.ErrAccountNotExist - } - oldAccount, err := GetAccount(ctx, name) - if err != nil { - log.Errorf(err, "get account [%s] failed", name) - return err - } - if len(a.Status) != 0 { - oldAccount.Status = a.Status - } - if len(a.Roles) != 0 { - oldAccount.Roles = a.Roles - } - err = datasource.Instance().UpdateAccount(ctx, name, oldAccount) - if err != nil { - log.Errorf(err, "can not edit account info") - return err - } - log.Infof("account [%s] is edit", oldAccount.ID) - return nil -} - -func GetAccount(ctx context.Context, name string) (*rbacmodel.Account, error) { - return datasource.Instance().GetAccount(ctx, name) -} -func ListAccount(ctx context.Context) ([]*rbacmodel.Account, int64, error) { - return datasource.Instance().ListAccount(ctx) -} -func AccountExist(ctx context.Context, name string) (bool, error) { - return datasource.Instance().AccountExist(ctx, name) -} -func DeleteAccount(ctx context.Context, name string) (bool, error) { - return datasource.Instance().DeleteAccount(ctx, []string{name}) -} - -//CreateAccount save 2 kv -//1. account info -func EditAccount(ctx context.Context, a *rbacmodel.Account) error { - exist, err := datasource.Instance().AccountExist(ctx, a.Name) - if err != nil { - log.Errorf(err, "can not edit account info") - return err - } - if !exist { - return datasource.ErrAccountCanNotEdit - } - - err = datasource.Instance().UpdateAccount(ctx, a.Name, a) - if err != nil { - log.Errorf(err, "can not edit account info") - return err - } - log.Infof("account [%s] is edit", a.ID) - return nil -} diff --git a/server/service/rbac/dao/account_dao_test.go b/server/service/rbac/dao/account_dao_test.go deleted file mode 100644 index 79a48a3..0000000 --- a/server/service/rbac/dao/account_dao_test.go +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 dao_test - -// initialize -import ( - "context" - "testing" - - "github.com/apache/servicecomb-service-center/server/service/rbac/dao" - _ "github.com/apache/servicecomb-service-center/test" - - "github.com/astaxie/beego" - "github.com/go-chassis/cari/rbac" - "github.com/stretchr/testify/assert" - "golang.org/x/crypto/bcrypt" -) - -func init() { - beego.AppConfig.Set("registry_plugin", "etcd") -} - -func newAccount(name string) *rbac.Account { - return &rbac.Account{ - Name: name, - Password: "Ab@11111", - Roles: []string{rbac.RoleAdmin}, - } -} - -func TestAccountDao_CreateAccount(t *testing.T) { - account := newAccount("createAccountTest") - dao.DeleteAccount(context.TODO(), account.Name) - _ = dao.CreateAccount(context.Background(), account) - t.Run("get account", func(t *testing.T) { - r, err := dao.GetAccount(context.Background(), account.Name) - assert.NoError(t, err) - assert.Equal(t, account.Name, r.Name) - hash, err := bcrypt.GenerateFromPassword([]byte(account.Password), 14) - err = bcrypt.CompareHashAndPassword(hash, []byte(account.Password)) - assert.NoError(t, err) - }) -} -func TestAccountDao_UpdateAccount(t *testing.T) { - account := newAccount("updateAccountTest") - t.Run("update an none exist account", func(t *testing.T) { - newAccount := &rbac.Account{Roles: []string{"admin"}} - err := dao.UpdateAccount(context.Background(), "noExist", newAccount) - assert.Error(t, err) - }) - - dao.DeleteAccount(context.TODO(), account.Name) - err := dao.CreateAccount(context.Background(), account) - assert.NoError(t, err) - - t.Run("update account", func(t *testing.T) { - newAccount := &rbac.Account{ - Roles: []string{rbac.RoleDeveloper}, - } - err = dao.UpdateAccount(context.Background(), account.Name, newAccount) - assert.NoError(t, err) - a, err := dao.GetAccount(context.Background(), account.Name) - assert.NoError(t, err) - assert.Equal(t, 1, len(a.Roles)) - assert.Equal(t, rbac.RoleDeveloper, a.Roles[0]) - }) -} diff --git a/server/service/rbac/password.go b/server/service/rbac/password.go index a8d5dd3..aaf4e21 100644 --- a/server/service/rbac/password.go +++ b/server/service/rbac/password.go @@ -27,7 +27,6 @@ import ( "golang.org/x/crypto/bcrypt" "github.com/apache/servicecomb-service-center/pkg/log" - "github.com/apache/servicecomb-service-center/server/service/rbac/dao" ) func ChangePassword(ctx context.Context, changerRole []string, changerName string, a *rbacmodel.Account) error { @@ -49,7 +48,7 @@ func ChangePassword(ctx context.Context, changerRole []string, changerName strin return ErrNoPermChangeAccount } func changePasswordForcibly(ctx context.Context, name, pwd string) error { - old, err := dao.GetAccount(ctx, name) + old, err := GetAccount(ctx, name) if err != nil { log.Error("can not change pwd", err) return err @@ -64,7 +63,7 @@ func changePassword(ctx context.Context, name, currentPassword, pwd string) erro if currentPassword == pwd { return ErrSamePassword } - old, err := dao.GetAccount(ctx, name) + old, err := GetAccount(ctx, name) if err != nil { log.Error("can not change pwd", err) return err @@ -88,7 +87,7 @@ func doChangePassword(ctx context.Context, old *rbacmodel.Account, pwd string) e return err } old.Password = stringutil.Bytes2str(hash) - err = dao.EditAccount(ctx, old) + err = EditAccount(ctx, old) if err != nil { log.Error("can not change pwd", err) return err diff --git a/server/service/rbac/rbac.go b/server/service/rbac/rbac.go index 4807a34..d72597c 100644 --- a/server/service/rbac/rbac.go +++ b/server/service/rbac/rbac.go @@ -21,8 +21,7 @@ import ( "context" "crypto/rsa" "errors" - "github.com/apache/servicecomb-service-center/datasource" - "github.com/apache/servicecomb-service-center/server/service/validator" + "github.com/go-chassis/cari/pkg/errsvc" "io/ioutil" "github.com/go-chassis/cari/rbac" @@ -30,7 +29,6 @@ import ( "github.com/apache/servicecomb-service-center/pkg/log" "github.com/apache/servicecomb-service-center/server/config" "github.com/apache/servicecomb-service-center/server/plugin/security/cipher" - "github.com/apache/servicecomb-service-center/server/service/rbac/dao" "github.com/go-chassis/go-archaius" "github.com/go-chassis/go-chassis/v2/security/authr" "github.com/go-chassis/go-chassis/v2/security/secret" @@ -61,7 +59,9 @@ func Init() { if err != nil { log.Fatal("can not enable auth module", err) } - accountExist, err := dao.AccountExist(context.Background(), RootName) + // role init before account + initBuildInRole() + accountExist, err := AccountExist(context.Background(), RootName) if err != nil { log.Fatal("can not enable auth module", err) } @@ -70,7 +70,6 @@ func Init() { } readPrivateKey() readPublicKey() - initBuildInRole() rbac.Add2WhiteAPIList(APITokenGranter) log.Info("rbac is enabled") } @@ -118,19 +117,17 @@ func initFirstTime(admin string) { Password: pwd, Roles: []string{rbac.RoleAdmin}, } - err = validator.ValidateCreateAccount(a) - if err != nil { - log.Fatal("invalid pwd", err) + err = CreateAccount(context.Background(), a) + if err == nil { + log.Info("root account init success") return } - if err := dao.CreateAccount(context.Background(), a); err != nil { - if err == datasource.ErrAccountDuplicated { - log.Info("root account already exists") - return - } - log.Fatal("can not enable rbac, init root account failed", err) + svcErr, ok := err.(*errsvc.Error) + if ok && svcErr.Code == rbac.ErrAccountConflict { + log.Info("root account already exist") + return } - log.Info("root account init success") + log.Fatal("can not enable rbac, init root account failed", err) } func getPassword() (string, error) { diff --git a/server/service/rbac/rbac_test.go b/server/service/rbac/rbac_test.go index 65ae32f..fd55df6 100644 --- a/server/service/rbac/rbac_test.go +++ b/server/service/rbac/rbac_test.go @@ -22,7 +22,6 @@ import ( "github.com/apache/servicecomb-service-center/pkg/privacy" "github.com/apache/servicecomb-service-center/server/config" rbacsvc "github.com/apache/servicecomb-service-center/server/service/rbac" - "github.com/apache/servicecomb-service-center/server/service/rbac/dao" _ "github.com/apache/servicecomb-service-center/test" "github.com/astaxie/beego" "github.com/go-chassis/cari/rbac" @@ -62,18 +61,10 @@ func init() { } archaius.Set(rbacsvc.InitPassword, "Complicated_password1") - dao.DeleteAccount(context.Background(), "root") - dao.DeleteAccount(context.Background(), "a") - dao.DeleteAccount(context.Background(), "b") - rbacsvc.Init() } func TestInitRBAC(t *testing.T) { - a, err := dao.GetAccount(context.Background(), "root") - assert.NoError(t, err) - assert.Equal(t, "root", a.Name) - t.Run("login and authenticate", func(t *testing.T) { token, err := authr.Login(context.Background(), "root", "Complicated_password1") assert.NoError(t, err) @@ -87,29 +78,24 @@ func TestInitRBAC(t *testing.T) { }) t.Run("change pwd,admin can change any one password", func(t *testing.T) { - persisted := &rbac.Account{Name: "a", Password: "Complicated_password1"} - err := dao.CreateAccount(context.Background(), persisted) + persisted := newAccount("admin_change_other_pwd") + err := rbacsvc.CreateAccount(context.Background(), persisted) assert.NoError(t, err) - err = rbacsvc.ChangePassword(context.Background(), []string{rbac.RoleAdmin}, "admin", &rbac.Account{Name: "a", Password: "Complicated_password2"}) + err = rbacsvc.ChangePassword(context.Background(), []string{rbac.RoleAdmin}, rbac.RoleAdmin, &rbac.Account{Name: persisted.Name, Password: "Complicated_password2"}) assert.NoError(t, err) - a, err := dao.GetAccount(context.Background(), "a") + a, err := rbacsvc.GetAccount(context.Background(), persisted.Name) assert.NoError(t, err) assert.True(t, privacy.SamePassword(a.Password, "Complicated_password2")) }) t.Run("change self password", func(t *testing.T) { - err := dao.CreateAccount(context.Background(), &rbac.Account{Name: "b", Password: "Complicated_password1"}) - assert.NoError(t, err) - err = rbacsvc.ChangePassword(context.Background(), nil, "b", &rbac.Account{Name: "b", CurrentPassword: "Complicated_password1", Password: "Complicated_password2"}) + a := newAccount("change_self_pwd") + err := rbacsvc.CreateAccount(context.Background(), a) assert.NoError(t, err) - a, err := dao.GetAccount(context.Background(), "b") + err = rbacsvc.ChangePassword(context.Background(), nil, a.Name, &rbac.Account{Name: a.Name, CurrentPassword: testPwd0, Password: testPwd1}) assert.NoError(t, err) - assert.True(t, privacy.SamePassword(a.Password, "Complicated_password2")) - - }) - t.Run("list kv", func(t *testing.T) { - _, n, err := dao.ListAccount(context.TODO()) + resp, err := rbacsvc.GetAccount(context.Background(), a.Name) assert.NoError(t, err) - assert.Greater(t, n, int64(2)) + assert.True(t, privacy.SamePassword(resp.Password, testPwd1)) }) } func BenchmarkAuthResource_Login(b *testing.B) { diff --git a/server/service/rbac/role.go b/server/service/rbac/role.go index 1523579..8753706 100644 --- a/server/service/rbac/role.go +++ b/server/service/rbac/role.go @@ -22,7 +22,6 @@ import ( "github.com/go-chassis/cari/rbac" "github.com/apache/servicecomb-service-center/pkg/log" - "github.com/apache/servicecomb-service-center/server/service/rbac/dao" ) var roleMap = map[string]*rbac.Role{} @@ -46,7 +45,7 @@ func initBuildInRole() { } func createBuildInRole(r *rbac.Role) { - status, err := dao.CreateRole(context.Background(), r) + status, err := CreateRole(context.Background(), r) if err != nil { log.Fatalf(err, "create role [%s] failed", r.Name) return diff --git a/server/service/rbac/dao/role_dao.go b/server/service/rbac/role_dao.go similarity index 99% rename from server/service/rbac/dao/role_dao.go rename to server/service/rbac/role_dao.go index df8feef..b9f4e3d 100644 --- a/server/service/rbac/dao/role_dao.go +++ b/server/service/rbac/role_dao.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package dao +package rbac import ( "context" diff --git a/server/service/rbac/dao/role_dao_test.go b/server/service/rbac/role_dao_test.go similarity index 70% rename from server/service/rbac/dao/role_dao_test.go rename to server/service/rbac/role_dao_test.go index 443332f..995a576 100644 --- a/server/service/rbac/dao/role_dao_test.go +++ b/server/service/rbac/role_dao_test.go @@ -1,16 +1,15 @@ -package dao_test +package rbac_test import ( "context" rbacsvc "github.com/apache/servicecomb-service-center/server/service/rbac" - "github.com/apache/servicecomb-service-center/server/service/rbac/dao" "github.com/go-chassis/cari/discovery" "github.com/go-chassis/cari/rbac" "github.com/stretchr/testify/assert" "testing" ) -func exampleRole(name string) *rbac.Role { +func newRole(name string) *rbac.Role { return &rbac.Role{ Name: name, Perms: []*rbac.Permission{ @@ -28,18 +27,18 @@ func exampleRole(name string) *rbac.Role { func TestCreateRole(t *testing.T) { t.Run("create new role, should succeed", func(t *testing.T) { - r := exampleRole("TestCreateRole_createNewRole") - status, err := dao.CreateRole(context.TODO(), r) + r := newRole("TestCreateRole_createNewRole") + status, err := rbacsvc.CreateRole(context.TODO(), r) assert.Nil(t, err) assert.True(t, status.IsSucceed()) }) t.Run("create role twice, should return: "+rbac.NewError(rbac.ErrRoleConflict, "").Error(), func(t *testing.T) { - r := exampleRole("TestCreateRole_createRoleTwice") - status, err := dao.CreateRole(context.TODO(), r) + r := newRole("TestCreateRole_createRoleTwice") + status, err := rbacsvc.CreateRole(context.TODO(), r) assert.Nil(t, err) assert.True(t, status.IsSucceed()) // twice - status, err = dao.CreateRole(context.TODO(), r) + status, err = rbacsvc.CreateRole(context.TODO(), r) assert.Nil(t, err) assert.Equal(t, rbac.ErrRoleConflict, status.GetCode()) }) @@ -47,17 +46,17 @@ func TestCreateRole(t *testing.T) { func TestGetRole(t *testing.T) { t.Run("get no exist role, should return: "+rbac.NewError(rbac.ErrRoleNotExist, "").Error(), func(t *testing.T) { - r, status, err := dao.GetRole(context.TODO(), "TestGetRole_getNoExistRole") + r, status, err := rbacsvc.GetRole(context.TODO(), "TestGetRole_getNoExistRole") assert.Nil(t, err) assert.Equal(t, rbac.ErrRoleNotExist, status.GetCode()) assert.Nil(t, r) }) t.Run("get exist role, should success", func(t *testing.T) { - r := exampleRole("TestGetRole_getExistRole") - status, err := dao.CreateRole(context.TODO(), r) + r := newRole("TestGetRole_getExistRole") + status, err := rbacsvc.CreateRole(context.TODO(), r) assert.Nil(t, err) assert.True(t, status.IsSucceed()) - resp, status, err := dao.GetRole(context.TODO(), r.Name) + resp, status, err := rbacsvc.GetRole(context.TODO(), r.Name) assert.Nil(t, err) assert.True(t, status.IsSucceed()) assert.Equal(t, r.Name, resp.Name) @@ -66,14 +65,14 @@ func TestGetRole(t *testing.T) { func TestEditRole(t *testing.T) { t.Run("edit no exist role, should return: "+rbac.NewError(rbac.ErrRoleNotExist, "").Error(), func(t *testing.T) { - r := exampleRole("TestEditRole_editNoExistRole") - status, err := dao.EditRole(context.TODO(), r.Name, r) + r := newRole("TestEditRole_editNoExistRole") + status, err := rbacsvc.EditRole(context.TODO(), r.Name, r) assert.Nil(t, err) assert.Equal(t, rbac.ErrRoleNotExist, status.GetCode()) }) t.Run("edit role, should success", func(t *testing.T) { - r := exampleRole("TestGetRole_editRole") - status, err := dao.CreateRole(context.TODO(), r) + r := newRole("TestGetRole_editRole") + status, err := rbacsvc.CreateRole(context.TODO(), r) assert.Nil(t, err) assert.True(t, status.IsSucceed()) @@ -97,18 +96,18 @@ func TestEditRole(t *testing.T) { Verbs: []string{"*"}, }, } - status, err = dao.EditRole(context.TODO(), r.Name, r) + status, err = rbacsvc.EditRole(context.TODO(), r.Name, r) assert.Nil(t, err) assert.True(t, status.IsSucceed()) - resp, status, err := dao.GetRole(context.TODO(), r.Name) + resp, status, err := rbacsvc.GetRole(context.TODO(), r.Name) assert.Nil(t, err) assert.True(t, status.IsSucceed()) assert.Equal(t, 2, len(resp.Perms)) }) t.Run("edit build in role, should return: "+discovery.NewError(discovery.ErrForbidden, "").Error(), func(t *testing.T) { for _, name := range []string{rbac.RoleDeveloper, rbac.RoleDeveloper} { - status, err := dao.EditRole(context.TODO(), name, exampleRole("")) + status, err := rbacsvc.EditRole(context.TODO(), name, newRole("")) assert.Nil(t, err) assert.Equal(t, discovery.ErrForbidden, status.GetCode()) } @@ -117,27 +116,27 @@ func TestEditRole(t *testing.T) { func TestDeleteRole(t *testing.T) { t.Run("delete no exist role, should return: "+rbac.NewError(rbac.ErrRoleNotExist, "").Error(), func(t *testing.T) { - status, err := dao.DeleteRole(context.TODO(), "TestDeleteRole_deleteNoExistRole") + status, err := rbacsvc.DeleteRole(context.TODO(), "TestDeleteRole_deleteNoExistRole") assert.Nil(t, err) assert.Equal(t, rbac.ErrRoleNotExist, status.GetCode()) }) t.Run("delete role, should success", func(t *testing.T) { - r := exampleRole("TestDeleteRole_deleteRole") - status, err := dao.CreateRole(context.TODO(), r) + r := newRole("TestDeleteRole_deleteRole") + status, err := rbacsvc.CreateRole(context.TODO(), r) assert.Nil(t, err) assert.True(t, status.IsSucceed()) - status, err = dao.DeleteRole(context.TODO(), r.Name) + status, err = rbacsvc.DeleteRole(context.TODO(), r.Name) assert.Nil(t, err) assert.True(t, status.IsSucceed()) - exist, err := dao.RoleExist(context.TODO(), r.Name) + exist, err := rbacsvc.RoleExist(context.TODO(), r.Name) assert.Nil(t, err) assert.False(t, exist) }) t.Run("delete build in role, should return: "+discovery.NewError(discovery.ErrForbidden, "").Error(), func(t *testing.T) { for _, name := range []string{rbac.RoleDeveloper, rbac.RoleDeveloper} { - status, err := dao.DeleteRole(context.TODO(), name) + status, err := rbacsvc.DeleteRole(context.TODO(), name) assert.Nil(t, err) assert.Equal(t, discovery.ErrForbidden, status.GetCode()) } @@ -146,7 +145,7 @@ func TestDeleteRole(t *testing.T) { func TestListRole(t *testing.T) { t.Run("list role, should success", func(t *testing.T) { - roles, total, err := dao.ListRole(context.TODO()) + roles, total, err := rbacsvc.ListRole(context.TODO()) assert.Nil(t, err) assert.True(t, total > 0) assert.Equal(t, int64(len(roles)), total) @@ -155,7 +154,7 @@ func TestListRole(t *testing.T) { func TestRoleExistt(t *testing.T) { t.Run("check no exist role, should success and not exist", func(t *testing.T) { - exist, err := dao.RoleExist(context.TODO(), "TestRoleExist_checkNoExistRole") + exist, err := rbacsvc.RoleExist(context.TODO(), "TestRoleExist_checkNoExistRole") assert.Nil(t, err) assert.False(t, exist) })