From 9b13bb335d98a594b52fadd7ff71f6e9d7351d55 Mon Sep 17 00:00:00 2001 From: sthuang <167743503+shaoting-huang@users.noreply.github.com> Date: Sat, 9 Nov 2024 08:44:28 +0800 Subject: [PATCH 1/2] RBAC custom privilege group Signed-off-by: shaoting-huang --- go.mod | 3 +- go.sum | 6 +- internal/datacoord/mock_test.go | 16 + internal/datanode/importv2/pool_test.go | 3 +- .../distributed/proxy/httpserver/constant.go | 46 +-- .../proxy/httpserver/handler_v2.go | 86 +++++ .../proxy/httpserver/handler_v2_test.go | 28 +- .../proxy/httpserver/request_v2.go | 5 + internal/distributed/proxy/service.go | 16 + .../distributed/rootcoord/client/client.go | 48 +++ .../rootcoord/client/client_test.go | 28 ++ internal/distributed/rootcoord/service.go | 16 + internal/metastore/catalog.go | 5 + internal/metastore/kv/rootcoord/kv_catalog.go | 124 ++++++- .../metastore/kv/rootcoord/kv_catalog_test.go | 173 +++++++++- .../kv/rootcoord/rootcoord_constant.go | 7 + .../metastore/mocks/mock_rootcoord_catalog.go | 195 +++++++++++ internal/mocks/mock_proxy.go | 220 ++++++++++++ internal/mocks/mock_querynode.go | 4 - internal/mocks/mock_querynode_client.go | 4 - internal/mocks/mock_rootcoord.go | 220 ++++++++++++ internal/mocks/mock_rootcoord_client.go | 280 +++++++++++++++ internal/proto/internal.proto | 2 + internal/proto/root_coord.proto | 4 + internal/proxy/impl.go | 121 +++++++ internal/proxy/meta_cache.go | 10 +- internal/proxy/rootcoord_mock_test.go | 16 + internal/proxy/util.go | 8 - internal/proxy/util_test.go | 4 - internal/rootcoord/meta_table.go | 191 +++++++++++ internal/rootcoord/meta_table_test.go | 41 +++ internal/rootcoord/mock_test.go | 40 +++ internal/rootcoord/mocks/meta_table.go | 311 +++++++++++++++++ internal/rootcoord/root_coord.go | 318 ++++++++++++++++-- internal/rootcoord/root_coord_test.go | 39 +++ internal/util/mock/grpc_rootcoord_client.go | 16 + pkg/metrics/rootcoord_metrics.go | 9 + pkg/util/constant.go | 21 ++ pkg/util/funcutil/policy.go | 11 + .../integration/rbac/privilege_group_test.go | 248 +++++++++++--- tests/integration/rbac/rbac_backup_test.go | 179 ++++++---- 41 files changed, 2914 insertions(+), 208 deletions(-) diff --git a/go.mod b/go.mod index 1f209810b967a..26800de17a7b3 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/klauspost/compress v1.17.9 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d - github.com/milvus-io/milvus-proto/go-api/v2 v2.4.16-0.20241106083218-955997f1a757 + github.com/milvus-io/milvus-proto/go-api/v2 v2.4.16-0.20241110064419-549e4694a7e7 github.com/minio/minio-go/v7 v7.0.73 github.com/pingcap/log v1.1.1-0.20221015072633-39906604fb81 github.com/prometheus/client_golang v1.14.0 @@ -153,6 +153,7 @@ require ( github.com/mattn/go-colorable v0.1.11 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/milvus-io/milvus-proto v2.2.9+incompatible // indirect github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect github.com/minio/highwayhash v1.0.2 // indirect diff --git a/go.sum b/go.sum index 2d0b44e3dad02..191d54286ab29 100644 --- a/go.sum +++ b/go.sum @@ -608,8 +608,10 @@ github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119 h1:9VXijWu github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg= github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b h1:TfeY0NxYxZzUfIfYe5qYDBzt4ZYRqzUjTR6CvUzjat8= github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b/go.mod h1:iwW+9cWfIzzDseEBCCeDSN5SD16Tidvy8cwQ7ZY8Qj4= -github.com/milvus-io/milvus-proto/go-api/v2 v2.4.16-0.20241106083218-955997f1a757 h1:t7B2lyq//BG8S+azUNEfohYxRtU5V9NAy8z0G+QAPo4= -github.com/milvus-io/milvus-proto/go-api/v2 v2.4.16-0.20241106083218-955997f1a757/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= +github.com/milvus-io/milvus-proto v2.2.9+incompatible h1:PYDvPc2U4yJoQyTjuFctGT5QSh7hcLyoQJUGQHhI+lY= +github.com/milvus-io/milvus-proto v2.2.9+incompatible/go.mod h1:urRUo4TMLWgew3AYu4Ak1oYr7KcV6AO2momWggKrDIw= +github.com/milvus-io/milvus-proto/go-api/v2 v2.4.16-0.20241110064419-549e4694a7e7 h1:gq5xxDS2EIYVk3ujO+sQgDWrhTTpsmV+r6Gm7dfFrt8= +github.com/milvus-io/milvus-proto/go-api/v2 v2.4.16-0.20241110064419-549e4694a7e7/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= github.com/milvus-io/milvus-storage/go v0.0.0-20231227072638-ebd0b8e56d70 h1:Z+sp64fmAOxAG7mU0dfVOXvAXlwRB0c8a96rIM5HevI= github.com/milvus-io/milvus-storage/go v0.0.0-20231227072638-ebd0b8e56d70/go.mod h1:GPETMcTZq1gLY1WA6Na5kiNAKnq8SEMMiVKUZrM3sho= github.com/milvus-io/pulsar-client-go v0.6.10 h1:eqpJjU+/QX0iIhEo3nhOqMNXL+TyInAs1IAHZCrCM/A= diff --git a/internal/datacoord/mock_test.go b/internal/datacoord/mock_test.go index aa0aee9c27e94..5012e309fca51 100644 --- a/internal/datacoord/mock_test.go +++ b/internal/datacoord/mock_test.go @@ -724,6 +724,22 @@ func (m *mockRootCoordClient) ListPolicy(ctx context.Context, in *internalpb.Lis return &internalpb.ListPolicyResponse{Status: &commonpb.Status{ErrorCode: commonpb.ErrorCode_Success}}, nil } +func (m *mockRootCoordClient) CreatePrivilegeGroup(ctx context.Context, req *milvuspb.CreatePrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + panic("implement me") +} + +func (m *mockRootCoordClient) DropPrivilegeGroup(ctx context.Context, req *milvuspb.DropPrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + panic("implement me") +} + +func (m *mockRootCoordClient) ListPrivilegeGroups(ctx context.Context, req *milvuspb.ListPrivilegeGroupsRequest, opts ...grpc.CallOption) (*milvuspb.ListPrivilegeGroupsResponse, error) { + panic("implement me") +} + +func (m *mockRootCoordClient) OperatePrivilegeGroup(ctx context.Context, req *milvuspb.OperatePrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + panic("implement me") +} + type mockHandler struct { meta *meta } diff --git a/internal/datanode/importv2/pool_test.go b/internal/datanode/importv2/pool_test.go index 4449a5031c812..06873c6d31ae5 100644 --- a/internal/datanode/importv2/pool_test.go +++ b/internal/datanode/importv2/pool_test.go @@ -20,10 +20,9 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/assert" - "github.com/milvus-io/milvus/pkg/config" "github.com/milvus-io/milvus/pkg/util/paramtable" + "github.com/stretchr/testify/assert" ) func TestResizePools(t *testing.T) { diff --git a/internal/distributed/proxy/httpserver/constant.go b/internal/distributed/proxy/httpserver/constant.go index 55b37063aa68a..4d4a3f8fcb2cb 100644 --- a/internal/distributed/proxy/httpserver/constant.go +++ b/internal/distributed/proxy/httpserver/constant.go @@ -9,14 +9,15 @@ import ( // v2 const ( // --- category --- - CollectionCategory = "/collections/" - EntityCategory = "/entities/" - PartitionCategory = "/partitions/" - UserCategory = "/users/" - RoleCategory = "/roles/" - IndexCategory = "/indexes/" - AliasCategory = "/aliases/" - ImportJobCategory = "/jobs/import/" + CollectionCategory = "/collections/" + EntityCategory = "/entities/" + PartitionCategory = "/partitions/" + UserCategory = "/users/" + RoleCategory = "/roles/" + IndexCategory = "/indexes/" + AliasCategory = "/aliases/" + ImportJobCategory = "/jobs/import/" + PrivilegeGroupCategory = "/privilege_groups/" ListAction = "list" HasAction = "has" @@ -37,13 +38,15 @@ const ( AdvancedSearchAction = "advanced_search" HybridSearchAction = "hybrid_search" - UpdatePasswordAction = "update_password" - GrantRoleAction = "grant_role" - RevokeRoleAction = "revoke_role" - GrantPrivilegeAction = "grant_privilege" - RevokePrivilegeAction = "revoke_privilege" - AlterAction = "alter" - GetProgressAction = "get_progress" // deprecated, keep it for compatibility, use `/v2/vectordb/jobs/import/describe` instead + UpdatePasswordAction = "update_password" + GrantRoleAction = "grant_role" + RevokeRoleAction = "revoke_role" + GrantPrivilegeAction = "grant_privilege" + RevokePrivilegeAction = "revoke_privilege" + AlterAction = "alter" + GetProgressAction = "get_progress" // deprecated, keep it for compatibility, use `/v2/vectordb/jobs/import/describe` instead + AddPrivilegesToGroupAction = "add_privileges_to_group" + RemovePrivilegesFromGroupAction = "remove_privileges_from_group" ) const ( @@ -116,11 +119,14 @@ const ( HTTPReturnRowCount = "rowCount" - HTTPReturnObjectType = "objectType" - HTTPReturnObjectName = "objectName" - HTTPReturnPrivilege = "privilege" - HTTPReturnGrantor = "grantor" - HTTPReturnDbName = "dbName" + HTTPReturnObjectType = "objectType" + HTTPReturnObjectName = "objectName" + HTTPReturnPrivilege = "privilege" + HTTPReturnGrantor = "grantor" + HTTPReturnDbName = "dbName" + HTTPReturnPrivilegeGroupName = "privilegeGroupName" + HTTPReturnPrivileges = "privileges" + HTTPReturnPrivilegeGroups = "privilegeGroups" DefaultMetricType = metric.COSINE DefaultPrimaryFieldName = "id" diff --git a/internal/distributed/proxy/httpserver/handler_v2.go b/internal/distributed/proxy/httpserver/handler_v2.go index 5deb4bd0f9408..24092e75d5ca9 100644 --- a/internal/distributed/proxy/httpserver/handler_v2.go +++ b/internal/distributed/proxy/httpserver/handler_v2.go @@ -132,6 +132,13 @@ func (h *HandlersV2) RegisterRoutesToV2(router gin.IRouter) { router.POST(RoleCategory+GrantPrivilegeAction, timeoutMiddleware(wrapperPost(func() any { return &GrantReq{} }, wrapperTraceLog(h.addPrivilegeToRole)))) router.POST(RoleCategory+RevokePrivilegeAction, timeoutMiddleware(wrapperPost(func() any { return &GrantReq{} }, wrapperTraceLog(h.removePrivilegeFromRole)))) + // privilege group + router.POST(PrivilegeGroupCategory+CreateAction, timeoutMiddleware(wrapperPost(func() any { return &PrivilegeGroupReq{} }, wrapperTraceLog(h.createPrivilegeGroup)))) + router.POST(PrivilegeGroupCategory+DropAction, timeoutMiddleware(wrapperPost(func() any { return &PrivilegeGroupReq{} }, wrapperTraceLog(h.dropPrivilegeGroup)))) + router.POST(PrivilegeGroupCategory+ListAction, timeoutMiddleware(wrapperPost(func() any { return &PrivilegeGroupReq{} }, wrapperTraceLog(h.listPrivilegeGroups)))) + router.POST(PrivilegeGroupCategory+AddPrivilegesToGroupAction, timeoutMiddleware(wrapperPost(func() any { return &PrivilegeGroupReq{} }, wrapperTraceLog(h.addPrivilegesToGroup)))) + router.POST(PrivilegeGroupCategory+RemovePrivilegesFromGroupAction, timeoutMiddleware(wrapperPost(func() any { return &PrivilegeGroupReq{} }, wrapperTraceLog(h.removePrivilegesFromGroup)))) + router.POST(IndexCategory+ListAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionNameReq{} }, wrapperTraceLog(h.wrapperCheckDatabase(h.listIndexes))))) router.POST(IndexCategory+DescribeAction, timeoutMiddleware(wrapperPost(func() any { return &IndexReq{} }, wrapperTraceLog(h.wrapperCheckDatabase(h.describeIndex))))) @@ -1711,6 +1718,85 @@ func (h *HandlersV2) removePrivilegeFromRole(ctx context.Context, c *gin.Context return h.operatePrivilegeToRole(ctx, c, anyReq.(*GrantReq), milvuspb.OperatePrivilegeType_Revoke, dbName) } +func (h *HandlersV2) createPrivilegeGroup(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) { + httpReq := anyReq.(*PrivilegeGroupReq) + req := &milvuspb.CreatePrivilegeGroupRequest{ + GroupName: httpReq.PrivilegeGroupName, + } + resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/CreatePrivilegeGroup", func(reqCtx context.Context, req any) (interface{}, error) { + return h.proxy.CreatePrivilegeGroup(reqCtx, req.(*milvuspb.CreatePrivilegeGroupRequest)) + }) + if err == nil { + HTTPReturn(c, http.StatusOK, wrapperReturnDefault()) + } + return resp, err +} + +func (h *HandlersV2) dropPrivilegeGroup(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) { + httpReq := anyReq.(*PrivilegeGroupReq) + req := &milvuspb.DropPrivilegeGroupRequest{ + GroupName: httpReq.PrivilegeGroupName, + } + resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DropPrivilegeGroup", func(reqCtx context.Context, req any) (interface{}, error) { + return h.proxy.DropPrivilegeGroup(reqCtx, req.(*milvuspb.DropPrivilegeGroupRequest)) + }) + if err == nil { + HTTPReturn(c, http.StatusOK, wrapperReturnDefault()) + } + return resp, err +} + +func (h *HandlersV2) listPrivilegeGroups(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) { + req := &milvuspb.ListPrivilegeGroupsRequest{} + resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/ListPrivilegeGroups", func(reqCtx context.Context, req any) (interface{}, error) { + return h.proxy.ListPrivilegeGroups(reqCtx, req.(*milvuspb.ListPrivilegeGroupsRequest)) + }) + if err == nil { + privGroups := make([]map[string]interface{}, 0) + for _, group := range resp.(*milvuspb.ListPrivilegeGroupsResponse).PrivilegeGroups { + privileges := make([]string, len(group.Privileges)) + for i, privilege := range group.Privileges { + privileges[i] = privilege.Name + } + groupInfo := map[string]interface{}{ + HTTPReturnPrivilegeGroupName: group.GroupName, + HTTPReturnPrivileges: strings.Join(privileges, ","), + } + privGroups = append(privGroups, groupInfo) + } + HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: gin.H{ + HTTPReturnPrivilegeGroups: privGroups, + }}) + } + return resp, err +} + +func (h *HandlersV2) addPrivilegesToGroup(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) { + return h.operatePrivilegeGroup(ctx, c, anyReq, dbName, milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup) +} + +func (h *HandlersV2) removePrivilegesFromGroup(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) { + return h.operatePrivilegeGroup(ctx, c, anyReq, dbName, milvuspb.OperatePrivilegeGroupType_RemovePrivilegesFromGroup) +} + +func (h *HandlersV2) operatePrivilegeGroup(ctx context.Context, c *gin.Context, anyReq any, dbName string, operateType milvuspb.OperatePrivilegeGroupType) (interface{}, error) { + httpReq := anyReq.(*PrivilegeGroupReq) + req := &milvuspb.OperatePrivilegeGroupRequest{ + GroupName: httpReq.PrivilegeGroupName, + Privileges: lo.Map(httpReq.Privileges, func(p string, _ int) *milvuspb.PrivilegeEntity { + return &milvuspb.PrivilegeEntity{Name: p} + }), + Type: operateType, + } + resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/OperatePrivilegeGroup", func(reqCtx context.Context, req any) (interface{}, error) { + return h.proxy.OperatePrivilegeGroup(reqCtx, req.(*milvuspb.OperatePrivilegeGroupRequest)) + }) + if err == nil { + HTTPReturn(c, http.StatusOK, wrapperReturnDefault()) + } + return resp, err +} + func (h *HandlersV2) listIndexes(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) { collectionGetter, _ := anyReq.(requestutil.CollectionNameGetter) indexNames := []string{} diff --git a/internal/distributed/proxy/httpserver/handler_v2_test.go b/internal/distributed/proxy/httpserver/handler_v2_test.go index aca560b902598..96aaa4289f9ed 100644 --- a/internal/distributed/proxy/httpserver/handler_v2_test.go +++ b/internal/distributed/proxy/httpserver/handler_v2_test.go @@ -897,6 +897,10 @@ func TestMethodGet(t *testing.T) { Status: &StatusSuccess, Alias: DefaultAliasName, }, nil).Once() + mp.EXPECT().ListPrivilegeGroups(mock.Anything, mock.Anything).Return(&milvuspb.ListPrivilegeGroupsResponse{ + Status: &StatusSuccess, + PrivilegeGroups: []*milvuspb.PrivilegeGroupInfo{{GroupName: "group1", Privileges: []*milvuspb.PrivilegeEntity{{Name: "*"}}}}, + }, nil).Once() testEngine := initHTTPServerV2(mp, false) queryTestCases := []rawTestCase{} @@ -987,6 +991,9 @@ func TestMethodGet(t *testing.T) { queryTestCases = append(queryTestCases, rawTestCase{ path: versionalV2(AliasCategory, DescribeAction), }) + queryTestCases = append(queryTestCases, rawTestCase{ + path: versionalV2(PrivilegeGroupCategory, ListAction), + }) for _, testcase := range queryTestCases { t.Run(testcase.path, func(t *testing.T) { @@ -996,7 +1003,8 @@ func TestMethodGet(t *testing.T) { `"indexName": "` + DefaultIndexName + `",` + `"userName": "` + util.UserRoot + `",` + `"roleName": "` + util.RoleAdmin + `",` + - `"aliasName": "` + DefaultAliasName + `"` + + `"aliasName": "` + DefaultAliasName + `",` + + `"privilegeGroupName": "pg"` + `}`)) req := httptest.NewRequest(http.MethodPost, testcase.path, bodyReader) w := httptest.NewRecorder() @@ -1037,6 +1045,7 @@ func TestMethodDelete(t *testing.T) { mp.EXPECT().DropRole(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Once() mp.EXPECT().DropIndex(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Once() mp.EXPECT().DropAlias(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Once() + mp.EXPECT().DropPrivilegeGroup(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Once() testEngine := initHTTPServerV2(mp, false) queryTestCases := []rawTestCase{} queryTestCases = append(queryTestCases, rawTestCase{ @@ -1057,10 +1066,13 @@ func TestMethodDelete(t *testing.T) { queryTestCases = append(queryTestCases, rawTestCase{ path: versionalV2(AliasCategory, DropAction), }) + queryTestCases = append(queryTestCases, rawTestCase{ + path: versionalV2(PrivilegeGroupCategory, DropAction), + }) for _, testcase := range queryTestCases { t.Run(testcase.path, func(t *testing.T) { bodyReader := bytes.NewReader([]byte(`{"collectionName": "` + DefaultCollectionName + `", "partitionName": "` + DefaultPartitionName + - `", "userName": "` + util.UserRoot + `", "roleName": "` + util.RoleAdmin + `", "indexName": "` + DefaultIndexName + `", "aliasName": "` + DefaultAliasName + `"}`)) + `", "userName": "` + util.UserRoot + `", "roleName": "` + util.RoleAdmin + `", "indexName": "` + DefaultIndexName + `", "aliasName": "` + DefaultAliasName + `", "privilegeGroupName": "pg"}`)) req := httptest.NewRequest(http.MethodPost, testcase.path, bodyReader) w := httptest.NewRecorder() testEngine.ServeHTTP(w, req) @@ -1099,6 +1111,8 @@ func TestMethodPost(t *testing.T) { mp.EXPECT().CreateIndex(mock.Anything, mock.Anything).Return(commonErrorStatus, nil).Once() mp.EXPECT().CreateAlias(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Once() mp.EXPECT().AlterAlias(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Once() + mp.EXPECT().CreatePrivilegeGroup(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Once() + mp.EXPECT().OperatePrivilegeGroup(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Twice() mp.EXPECT().ImportV2(mock.Anything, mock.Anything).Return(&internalpb.ImportResponse{ Status: commonSuccessStatus, JobID: "1234567890", }, nil).Once() @@ -1191,6 +1205,15 @@ func TestMethodPost(t *testing.T) { queryTestCases = append(queryTestCases, rawTestCase{ path: versionalV2(ImportJobCategory, DescribeAction), }) + queryTestCases = append(queryTestCases, rawTestCase{ + path: versionalV2(PrivilegeGroupCategory, CreateAction), + }) + queryTestCases = append(queryTestCases, rawTestCase{ + path: versionalV2(PrivilegeGroupCategory, AddPrivilegesToGroupAction), + }) + queryTestCases = append(queryTestCases, rawTestCase{ + path: versionalV2(PrivilegeGroupCategory, RemovePrivilegesFromGroupAction), + }) for _, testcase := range queryTestCases { t.Run(testcase.path, func(t *testing.T) { @@ -1201,6 +1224,7 @@ func TestMethodPost(t *testing.T) { `"indexParams": [{"indexName": "` + DefaultIndexName + `", "fieldName": "book_intro", "metricType": "L2", "params": {"nlist": 30, "index_type": "IVF_FLAT"}}],` + `"userName": "` + util.UserRoot + `", "password": "Milvus", "newPassword": "milvus", "roleName": "` + util.RoleAdmin + `",` + `"roleName": "` + util.RoleAdmin + `", "objectType": "Global", "objectName": "*", "privilege": "*",` + + `"privilegeGroupName": "pg", "privileges": ["create", "drop"],` + `"aliasName": "` + DefaultAliasName + `",` + `"jobId": "1234567890",` + `"files": [["book.json"]]` + diff --git a/internal/distributed/proxy/httpserver/request_v2.go b/internal/distributed/proxy/httpserver/request_v2.go index e7f1364a70ae9..fc7d82dc1fbd4 100644 --- a/internal/distributed/proxy/httpserver/request_v2.go +++ b/internal/distributed/proxy/httpserver/request_v2.go @@ -258,6 +258,11 @@ func (req *RoleReq) GetRoleName() string { return req.RoleName } +type PrivilegeGroupReq struct { + PrivilegeGroupName string `json:"privilegeGroupName" binding:"required"` + Privileges []string `json:"privileges"` +} + type GrantReq struct { RoleName string `json:"roleName" binding:"required"` ObjectType string `json:"objectType" binding:"required"` diff --git a/internal/distributed/proxy/service.go b/internal/distributed/proxy/service.go index 9b6a070c65d28..b941663cecc61 100644 --- a/internal/distributed/proxy/service.go +++ b/internal/distributed/proxy/service.go @@ -1060,6 +1060,22 @@ func (s *Server) RestoreRBAC(ctx context.Context, req *milvuspb.RestoreRBACMetaR return s.proxy.RestoreRBAC(ctx, req) } +func (s *Server) CreatePrivilegeGroup(ctx context.Context, req *milvuspb.CreatePrivilegeGroupRequest) (*commonpb.Status, error) { + return s.proxy.CreatePrivilegeGroup(ctx, req) +} + +func (s *Server) DropPrivilegeGroup(ctx context.Context, req *milvuspb.DropPrivilegeGroupRequest) (*commonpb.Status, error) { + return s.proxy.DropPrivilegeGroup(ctx, req) +} + +func (s *Server) ListPrivilegeGroups(ctx context.Context, req *milvuspb.ListPrivilegeGroupsRequest) (*milvuspb.ListPrivilegeGroupsResponse, error) { + return s.proxy.ListPrivilegeGroups(ctx, req) +} + +func (s *Server) OperatePrivilegeGroup(ctx context.Context, req *milvuspb.OperatePrivilegeGroupRequest) (*commonpb.Status, error) { + return s.proxy.OperatePrivilegeGroup(ctx, req) +} + func (s *Server) RefreshPolicyInfoCache(ctx context.Context, req *proxypb.RefreshPolicyInfoCacheRequest) (*commonpb.Status, error) { return s.proxy.RefreshPolicyInfoCache(ctx, req) } diff --git a/internal/distributed/rootcoord/client/client.go b/internal/distributed/rootcoord/client/client.go index fbf8d2657f16e..6de68c45f7d66 100644 --- a/internal/distributed/rootcoord/client/client.go +++ b/internal/distributed/rootcoord/client/client.go @@ -694,3 +694,51 @@ func (c *Client) RestoreRBAC(ctx context.Context, in *milvuspb.RestoreRBACMetaRe return client.RestoreRBAC(ctx, in) }) } + +func (c *Client) CreatePrivilegeGroup(ctx context.Context, in *milvuspb.CreatePrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + in = typeutil.Clone(in) + commonpbutil.UpdateMsgBase( + in.GetBase(), + commonpbutil.FillMsgBaseFromClient(paramtable.GetNodeID(), commonpbutil.WithTargetID(c.sess.ServerID)), + ) + + return wrapGrpcCall(ctx, c, func(client rootcoordpb.RootCoordClient) (*commonpb.Status, error) { + return client.CreatePrivilegeGroup(ctx, in) + }) +} + +func (c *Client) DropPrivilegeGroup(ctx context.Context, in *milvuspb.DropPrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + in = typeutil.Clone(in) + commonpbutil.UpdateMsgBase( + in.GetBase(), + commonpbutil.FillMsgBaseFromClient(paramtable.GetNodeID(), commonpbutil.WithTargetID(c.sess.ServerID)), + ) + + return wrapGrpcCall(ctx, c, func(client rootcoordpb.RootCoordClient) (*commonpb.Status, error) { + return client.DropPrivilegeGroup(ctx, in) + }) +} + +func (c *Client) ListPrivilegeGroups(ctx context.Context, in *milvuspb.ListPrivilegeGroupsRequest, opts ...grpc.CallOption) (*milvuspb.ListPrivilegeGroupsResponse, error) { + in = typeutil.Clone(in) + commonpbutil.UpdateMsgBase( + in.GetBase(), + commonpbutil.FillMsgBaseFromClient(paramtable.GetNodeID(), commonpbutil.WithTargetID(c.sess.ServerID)), + ) + + return wrapGrpcCall(ctx, c, func(client rootcoordpb.RootCoordClient) (*milvuspb.ListPrivilegeGroupsResponse, error) { + return client.ListPrivilegeGroups(ctx, in) + }) +} + +func (c *Client) OperatePrivilegeGroup(ctx context.Context, in *milvuspb.OperatePrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + in = typeutil.Clone(in) + commonpbutil.UpdateMsgBase( + in.GetBase(), + commonpbutil.FillMsgBaseFromClient(paramtable.GetNodeID(), commonpbutil.WithTargetID(c.sess.ServerID)), + ) + + return wrapGrpcCall(ctx, c, func(client rootcoordpb.RootCoordClient) (*commonpb.Status, error) { + return client.OperatePrivilegeGroup(ctx, in) + }) +} diff --git a/internal/distributed/rootcoord/client/client_test.go b/internal/distributed/rootcoord/client/client_test.go index 07c092e687439..5661298327fe6 100644 --- a/internal/distributed/rootcoord/client/client_test.go +++ b/internal/distributed/rootcoord/client/client_test.go @@ -236,10 +236,38 @@ func Test_NewClient(t *testing.T) { r, err := client.ListDatabases(ctx, nil) retCheck(retNotNil, r, err) } + { + r, err := client.AlterCollection(ctx, nil) + retCheck(retNotNil, r, err) + } { r, err := client.AlterDatabase(ctx, nil) retCheck(retNotNil, r, err) } + { + r, err := client.BackupRBAC(ctx, nil) + retCheck(retNotNil, r, err) + } + { + r, err := client.RestoreRBAC(ctx, nil) + retCheck(retNotNil, r, err) + } + { + r, err := client.CreatePrivilegeGroup(ctx, nil) + retCheck(retNotNil, r, err) + } + { + r, err := client.DropPrivilegeGroup(ctx, nil) + retCheck(retNotNil, r, err) + } + { + r, err := client.ListPrivilegeGroups(ctx, nil) + retCheck(retNotNil, r, err) + } + { + r, err := client.OperatePrivilegeGroup(ctx, nil) + retCheck(retNotNil, r, err) + } } client.(*Client).grpcClient = &mock.GRPCClientBase[rootcoordpb.RootCoordClient]{ diff --git a/internal/distributed/rootcoord/service.go b/internal/distributed/rootcoord/service.go index 8210cf3085727..4f245d1b07654 100644 --- a/internal/distributed/rootcoord/service.go +++ b/internal/distributed/rootcoord/service.go @@ -545,3 +545,19 @@ func (s *Server) BackupRBAC(ctx context.Context, request *milvuspb.BackupRBACMet func (s *Server) RestoreRBAC(ctx context.Context, request *milvuspb.RestoreRBACMetaRequest) (*commonpb.Status, error) { return s.rootCoord.RestoreRBAC(ctx, request) } + +func (s *Server) CreatePrivilegeGroup(ctx context.Context, request *milvuspb.CreatePrivilegeGroupRequest) (*commonpb.Status, error) { + return s.rootCoord.CreatePrivilegeGroup(ctx, request) +} + +func (s *Server) DropPrivilegeGroup(ctx context.Context, request *milvuspb.DropPrivilegeGroupRequest) (*commonpb.Status, error) { + return s.rootCoord.DropPrivilegeGroup(ctx, request) +} + +func (s *Server) ListPrivilegeGroups(ctx context.Context, request *milvuspb.ListPrivilegeGroupsRequest) (*milvuspb.ListPrivilegeGroupsResponse, error) { + return s.rootCoord.ListPrivilegeGroups(ctx, request) +} + +func (s *Server) OperatePrivilegeGroup(ctx context.Context, request *milvuspb.OperatePrivilegeGroupRequest) (*commonpb.Status, error) { + return s.rootCoord.OperatePrivilegeGroup(ctx, request) +} diff --git a/internal/metastore/catalog.go b/internal/metastore/catalog.go index b40ee8d5e7e79..2d23104912ee5 100644 --- a/internal/metastore/catalog.go +++ b/internal/metastore/catalog.go @@ -85,6 +85,11 @@ type RootCoordCatalog interface { BackupRBAC(ctx context.Context, tenant string) (*milvuspb.RBACMeta, error) RestoreRBAC(ctx context.Context, tenant string, meta *milvuspb.RBACMeta) error + GetPrivilegeGroup(ctx context.Context, groupName string) (*milvuspb.PrivilegeGroupInfo, error) + DropPrivilegeGroup(ctx context.Context, groupName string) error + SavePrivilegeGroup(ctx context.Context, data *milvuspb.PrivilegeGroupInfo) error + ListPrivilegeGroups(ctx context.Context) ([]*milvuspb.PrivilegeGroupInfo, error) + Close() } diff --git a/internal/metastore/kv/rootcoord/kv_catalog.go b/internal/metastore/kv/rootcoord/kv_catalog.go index 28705ff8964db..7ed617d00a9da 100644 --- a/internal/metastore/kv/rootcoord/kv_catalog.go +++ b/internal/metastore/kv/rootcoord/kv_catalog.go @@ -1295,10 +1295,16 @@ func (kc *Catalog) BackupRBAC(ctx context.Context, tenant string) (*milvuspb.RBA grantsEntity = append(grantsEntity, grants...) } + privGroups, err := kc.ListPrivilegeGroups(ctx) + if err != nil { + return nil, err + } + return &milvuspb.RBACMeta{ - Users: userInfos, - Roles: roleEntity, - Grants: grantsEntity, + Users: userInfos, + Roles: roleEntity, + Grants: grantsEntity, + PrivilegeGroups: privGroups, }, nil } @@ -1307,6 +1313,7 @@ func (kc *Catalog) RestoreRBAC(ctx context.Context, tenant string, meta *milvusp needRollbackUser := make([]*milvuspb.UserInfo, 0) needRollbackRole := make([]*milvuspb.RoleEntity, 0) needRollbackGrants := make([]*milvuspb.GrantEntity, 0) + needRollbackPrivilegeGroups := make([]*milvuspb.PrivilegeGroupInfo, 0) defer func() { if err != nil { log.Warn("failed to restore rbac, try to rollback", zap.Error(err)) @@ -1333,6 +1340,14 @@ func (kc *Catalog) RestoreRBAC(ctx context.Context, tenant string, meta *milvusp log.Warn("failed to rollback users after restore failed", zap.Error(err)) } } + + // roll back privilege group + for _, group := range needRollbackPrivilegeGroups { + err = kc.DropPrivilegeGroup(ctx, group.GroupName) + if err != nil { + log.Warn("failed to rollback privilege groups after restore failed", zap.Error(err)) + } + } } }() @@ -1355,9 +1370,42 @@ func (kc *Catalog) RestoreRBAC(ctx context.Context, tenant string, meta *milvusp needRollbackRole = append(needRollbackRole, role) } - // restore grant + // restore privilege group + existPrivGroups, err := kc.ListPrivilegeGroups(ctx) + if err != nil { + return err + } + existPrivGroupMap := lo.SliceToMap(existPrivGroups, func(entity *milvuspb.PrivilegeGroupInfo) (string, struct{}) { return entity.GroupName, struct{}{} }) + for _, group := range meta.PrivilegeGroups { + if _, ok := existPrivGroupMap[group.GroupName]; ok { + log.Warn("failed to restore, privilege group already exists", zap.String("group", group.GroupName)) + err = errors.Newf("privilege group [%s] already exists", group.GroupName) + return err + } + err = kc.SavePrivilegeGroup(ctx, group) + if err != nil { + return err + } + needRollbackPrivilegeGroups = append(needRollbackPrivilegeGroups, group) + } + + // restore grant, list latest privilege group first + existPrivGroups, err = kc.ListPrivilegeGroups(ctx) + if err != nil { + return err + } + existPrivGroupMap = lo.SliceToMap(existPrivGroups, func(entity *milvuspb.PrivilegeGroupInfo) (string, struct{}) { return entity.GroupName, struct{}{} }) for _, grant := range meta.Grants { - grant.Grantor.Privilege.Name = util.PrivilegeNameForMetastore(grant.Grantor.Privilege.Name) + privName := grant.Grantor.Privilege.Name + if util.IsPrivilegeNameDefined(privName) { + grant.Grantor.Privilege.Name = util.PrivilegeNameForMetastore(privName) + } else if _, ok := existPrivGroupMap[privName]; ok { + grant.Grantor.Privilege.Name = util.PrivilegeGroupNameForMetastore(privName) + } else { + log.Warn("failed to restore, privilege group does not exist", zap.String("group", privName)) + err = errors.Newf("privilege group [%s] does not exist", privName) + return err + } err = kc.AlterGrant(ctx, tenant, grant, milvuspb.OperatePrivilegeType_Grant) if err != nil { return err @@ -1402,6 +1450,72 @@ func (kc *Catalog) RestoreRBAC(ctx context.Context, tenant string, meta *milvusp return err } +func (kc *Catalog) GetPrivilegeGroup(ctx context.Context, groupName string) (*milvuspb.PrivilegeGroupInfo, error) { + k := BuildPrivilegeGroupkey(groupName) + val, err := kc.Txn.Load(k) + if err != nil { + if errors.Is(err, merr.ErrIoKeyNotFound) { + return nil, fmt.Errorf("privilege group [%s] does not exist", groupName) + } + log.Error("failed to load privilege group", zap.String("group", groupName), zap.Error(err)) + return nil, err + } + privGroupInfo := &milvuspb.PrivilegeGroupInfo{} + err = proto.Unmarshal([]byte(val), privGroupInfo) + if err != nil { + log.Error("failed to unmarshal privilege group info", zap.Error(err)) + return nil, err + } + return privGroupInfo, nil +} + +func (kc *Catalog) DropPrivilegeGroup(ctx context.Context, groupName string) error { + k := BuildPrivilegeGroupkey(groupName) + err := kc.Txn.Remove(k) + if err != nil { + log.Warn("fail to drop privilege group", zap.String("key", k), zap.Error(err)) + return err + } + return nil +} + +func (kc *Catalog) SavePrivilegeGroup(ctx context.Context, data *milvuspb.PrivilegeGroupInfo) error { + k := BuildPrivilegeGroupkey(data.GroupName) + groupInfo := &milvuspb.PrivilegeGroupInfo{ + GroupName: data.GroupName, + Privileges: lo.Uniq(data.Privileges), + } + v, err := proto.Marshal(groupInfo) + if err != nil { + log.Error("failed to marshal privilege group info", zap.Error(err)) + return err + } + if err = kc.Txn.Save(k, string(v)); err != nil { + log.Warn("fail to put privilege group", zap.String("key", k), zap.Error(err)) + return err + } + return nil +} + +func (kc *Catalog) ListPrivilegeGroups(ctx context.Context) ([]*milvuspb.PrivilegeGroupInfo, error) { + _, vals, err := kc.Txn.LoadWithPrefix(PrivilegeGroupPrefix) + if err != nil { + log.Error("failed to list privilege groups", zap.String("prefix", PrivilegeGroupPrefix), zap.Error(err)) + return nil, err + } + privGroups := make([]*milvuspb.PrivilegeGroupInfo, 0, len(vals)) + for _, val := range vals { + privGroupInfo := &milvuspb.PrivilegeGroupInfo{} + err = proto.Unmarshal([]byte(val), privGroupInfo) + if err != nil { + log.Error("failed to unmarshal privilege group info", zap.Error(err)) + return nil, err + } + privGroups = append(privGroups, privGroupInfo) + } + return privGroups, nil +} + func (kc *Catalog) Close() { // do nothing } diff --git a/internal/metastore/kv/rootcoord/kv_catalog_test.go b/internal/metastore/kv/rootcoord/kv_catalog_test.go index 71817ae1098b7..f65f522dd6904 100644 --- a/internal/metastore/kv/rootcoord/kv_catalog_test.go +++ b/internal/metastore/kv/rootcoord/kv_catalog_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/cockroachdb/errors" + "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -2595,6 +2596,11 @@ func TestRBAC_Backup(t *testing.T) { }) c.AlterUserRole(ctx, util.DefaultTenant, &milvuspb.UserEntity{Name: "user1"}, &milvuspb.RoleEntity{Name: "role1"}, milvuspb.OperateUserRoleType_AddUserToRole) + c.SavePrivilegeGroup(ctx, &milvuspb.PrivilegeGroupInfo{ + GroupName: "custom_group", + Privileges: []*milvuspb.PrivilegeEntity{{Name: "CreateCollection"}}, + }) + // test backup success backup, err := c.BackupRBAC(ctx, util.DefaultTenant) assert.NoError(t, err) @@ -2605,6 +2611,9 @@ func TestRBAC_Backup(t *testing.T) { assert.Equal(t, "user1", backup.Users[0].User) assert.Equal(t, 1, len(backup.Users[0].Roles)) assert.Equal(t, 1, len(backup.Roles)) + assert.Equal(t, 1, len(backup.PrivilegeGroups)) + assert.Equal(t, "custom_group", backup.PrivilegeGroups[0].GroupName) + assert.Equal(t, "CreateCollection", backup.PrivilegeGroups[0].Privileges[0].Name) } func TestRBAC_Restore(t *testing.T) { @@ -2650,10 +2659,17 @@ func TestRBAC_Restore(t *testing.T) { DbName: util.DefaultDBName, Grantor: &milvuspb.GrantorEntity{ User: &milvuspb.UserEntity{Name: "user1"}, - Privilege: &milvuspb.PrivilegeEntity{Name: "PrivilegeLoad"}, + Privilege: &milvuspb.PrivilegeEntity{Name: "Load"}, }, }, }, + + PrivilegeGroups: []*milvuspb.PrivilegeGroupInfo{ + { + GroupName: "custom_group", + Privileges: []*milvuspb.PrivilegeEntity{{Name: "CreateCollection"}}, + }, + }, } // test restore success err := c.RestoreRBAC(ctx, util.DefaultTenant, rbacMeta) @@ -2679,6 +2695,13 @@ func TestRBAC_Restore(t *testing.T) { assert.Equal(t, "obj_name1", grants[0].ObjectName) assert.Equal(t, "role1", grants[0].Role.Name) assert.Equal(t, "user1", grants[0].Grantor.User.Name) + assert.Equal(t, "Load", grants[0].Grantor.Privilege.Name) + // check privilege group + privGroups, err := c.ListPrivilegeGroups(ctx) + assert.NoError(t, err) + assert.Len(t, privGroups, 1) + assert.Equal(t, "custom_group", privGroups[0].GroupName) + assert.Equal(t, "CreateCollection", privGroups[0].Privileges[0].Name) rbacMeta2 := &milvuspb.RBACMeta{ Users: []*milvuspb.UserInfo{ @@ -2715,10 +2738,17 @@ func TestRBAC_Restore(t *testing.T) { DbName: util.DefaultDBName, Grantor: &milvuspb.GrantorEntity{ User: &milvuspb.UserEntity{Name: "user2"}, - Privilege: &milvuspb.PrivilegeEntity{Name: "PrivilegeLoad"}, + Privilege: &milvuspb.PrivilegeEntity{Name: "Load"}, }, }, }, + + PrivilegeGroups: []*milvuspb.PrivilegeGroupInfo{ + { + GroupName: "custom_group2", + Privileges: []*milvuspb.PrivilegeEntity{{Name: "DropCollection"}}, + }, + }, } // test restore failed and roll back @@ -2740,6 +2770,145 @@ func TestRBAC_Restore(t *testing.T) { }) assert.NoError(t, err) assert.Len(t, grants, 1) + assert.Equal(t, grants[0].Grantor.Privilege.Name, "Load") + // check privilege group + privGroups, err = c.ListPrivilegeGroups(ctx) + assert.NoError(t, err) + assert.Len(t, privGroups, 1) + assert.Equal(t, "custom_group", privGroups[0].GroupName) + assert.Equal(t, "CreateCollection", privGroups[0].Privileges[0].Name) +} + +func TestRBAC_PrivilegeGroup(t *testing.T) { + ctx := context.TODO() + group1 := "group1" + group2 := "group2" + key1 := BuildPrivilegeGroupkey(group1) + key2 := BuildPrivilegeGroupkey(group2) + privGroupInfo1 := &milvuspb.PrivilegeGroupInfo{GroupName: group1, Privileges: []*milvuspb.PrivilegeEntity{{Name: "priv10"}, {Name: "priv11"}}} + privGroupInfo2 := &milvuspb.PrivilegeGroupInfo{GroupName: group2, Privileges: []*milvuspb.PrivilegeEntity{{Name: "priv20"}, {Name: "priv21"}}} + v1, _ := proto.Marshal(privGroupInfo1) + v2, _ := proto.Marshal(privGroupInfo2) + + t.Run("test GetPrivilegeGroup", func(t *testing.T) { + var ( + kvmock = mocks.NewTxnKV(t) + c = &Catalog{Txn: kvmock} + ) + kvmock.EXPECT().Load(key1).Return(string(v1), nil) + kvmock.EXPECT().Load(key2).Return("", merr.ErrIoKeyNotFound) + + tests := []struct { + description string + expectedErr error + groupName string + expectedPrivileges []string + }{ + {"group not found", fmt.Errorf("privilege group [%s] does not exist", group2), group2, nil}, + {"valid group", nil, group1, []string{"priv10", "priv11"}}, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + group, err := c.GetPrivilegeGroup(ctx, test.groupName) + if test.expectedErr != nil { + assert.Error(t, err, test.expectedErr) + } else { + assert.NoError(t, err) + assert.ElementsMatch(t, getPrivilegeNames(group.Privileges), test.expectedPrivileges) + } + }) + } + }) + + t.Run("test DropPrivilegeGroup", func(t *testing.T) { + var ( + kvmock = mocks.NewTxnKV(t) + c = &Catalog{Txn: kvmock} + ) + + kvmock.EXPECT().Remove(key1).Return(nil) + kvmock.EXPECT().Remove(key2).Return(errors.New("Mock remove failure")) + + tests := []struct { + description string + isValid bool + groupName string + }{ + {"valid group", true, group1}, + {"remove failure", false, group2}, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + err := c.DropPrivilegeGroup(ctx, test.groupName) + if test.isValid { + assert.NoError(t, err) + } else { + assert.Error(t, err) + } + }) + } + }) + + t.Run("test SavePrivilegeGroup", func(t *testing.T) { + var ( + kvmock = mocks.NewTxnKV(t) + c = &Catalog{Txn: kvmock} + ) + + kvmock.EXPECT().Save(key1, mock.Anything).Return(nil) + kvmock.EXPECT().Save(key2, mock.Anything).Return(nil) + + tests := []struct { + description string + isValid bool + group *milvuspb.PrivilegeGroupInfo + }{ + {"valid group with existing key", true, &milvuspb.PrivilegeGroupInfo{GroupName: group1, Privileges: []*milvuspb.PrivilegeEntity{{Name: "priv10"}, {Name: "priv11"}}}}, + {"valid group without existing key", true, &milvuspb.PrivilegeGroupInfo{GroupName: group2, Privileges: []*milvuspb.PrivilegeEntity{{Name: "priv10"}, {Name: "priv11"}}}}, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + err := c.SavePrivilegeGroup(ctx, test.group) + if test.isValid { + assert.NoError(t, err) + } else { + assert.Error(t, err) + } + }) + } + }) + + t.Run("test ListPrivilegeGroups", func(t *testing.T) { + var ( + kvmock = mocks.NewTxnKV(t) + c = &Catalog{Txn: kvmock} + ) + + kvmock.EXPECT().LoadWithPrefix(PrivilegeGroupPrefix).Return( + []string{key1, key2}, + []string{string(v1), string(v2)}, + nil, + ) + groups, err := c.ListPrivilegeGroups(ctx) + assert.NoError(t, err) + groupNames := lo.Map(groups, func(g *milvuspb.PrivilegeGroupInfo, _ int) string { + return g.GroupName + }) + assert.ElementsMatch(t, groupNames, []string{group1, group2}) + assert.ElementsMatch(t, getPrivilegeNames(groups[0].Privileges), []string{"priv10", "priv11"}) + assert.ElementsMatch(t, getPrivilegeNames(groups[1].Privileges), []string{"priv20", "priv21"}) + }) +} + +func getPrivilegeNames(privileges []*milvuspb.PrivilegeEntity) []string { + if len(privileges) == 0 { + return []string{} + } + return lo.Map(privileges, func(p *milvuspb.PrivilegeEntity, _ int) string { + return p.Name + }) } func TestCatalog_AlterDatabase(t *testing.T) { diff --git a/internal/metastore/kv/rootcoord/rootcoord_constant.go b/internal/metastore/kv/rootcoord/rootcoord_constant.go index b250216048a37..9a912f76fc34b 100644 --- a/internal/metastore/kv/rootcoord/rootcoord_constant.go +++ b/internal/metastore/kv/rootcoord/rootcoord_constant.go @@ -49,6 +49,9 @@ const ( // GranteeIDPrefix prefix for mapping among privilege and grantor GranteeIDPrefix = ComponentPrefix + CommonCredentialPrefix + "/grantee-id" + + // PrivilegeGroupPrefix prefix for privilege group + PrivilegeGroupPrefix = ComponentPrefix + "/privilege-group" ) func BuildDatabasePrefixWithDBID(dbID int64) string { @@ -69,3 +72,7 @@ func getDatabasePrefix(dbID int64) string { } return CollectionMetaPrefix } + +func BuildPrivilegeGroupkey(groupName string) string { + return fmt.Sprintf("%s/%s", PrivilegeGroupPrefix, groupName) +} diff --git a/internal/metastore/mocks/mock_rootcoord_catalog.go b/internal/metastore/mocks/mock_rootcoord_catalog.go index ca2ed2f27f18a..646eb849ae756 100644 --- a/internal/metastore/mocks/mock_rootcoord_catalog.go +++ b/internal/metastore/mocks/mock_rootcoord_catalog.go @@ -1828,3 +1828,198 @@ func NewRootCoordCatalog(t interface { return mock } + +// GetPrivilegeGroup provides a mock function with given fields: ctx, groupName +func (_m *RootCoordCatalog) GetPrivilegeGroup(ctx context.Context, groupName string) (*milvuspb.PrivilegeGroupInfo, error) { + ret := _m.Called(ctx, groupName) + + var r0 *milvuspb.PrivilegeGroupInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*milvuspb.PrivilegeGroupInfo, error)); ok { + return rf(ctx, groupName) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *milvuspb.PrivilegeGroupInfo); ok { + r0 = rf(ctx, groupName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*milvuspb.PrivilegeGroupInfo) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, groupName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RootCoordCatalog_GetPrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPrivilegeGroup' +type RootCoordCatalog_GetPrivilegeGroup_Call struct { + *mock.Call +} + +// GetPrivilegeGroup is a helper method to define mock.On call +// - ctx context.Context +// - groupName string +func (_e *RootCoordCatalog_Expecter) GetPrivilegeGroup(ctx interface{}, groupName interface{}) *RootCoordCatalog_GetPrivilegeGroup_Call { + return &RootCoordCatalog_GetPrivilegeGroup_Call{Call: _e.mock.On("GetPrivilegeGroup", ctx, groupName)} +} + +func (_c *RootCoordCatalog_GetPrivilegeGroup_Call) Run(run func(ctx context.Context, groupName string)) *RootCoordCatalog_GetPrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *RootCoordCatalog_GetPrivilegeGroup_Call) Return(_a0 *milvuspb.PrivilegeGroupInfo, _a1 error) *RootCoordCatalog_GetPrivilegeGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RootCoordCatalog_GetPrivilegeGroup_Call) RunAndReturn(run func(context.Context, string) (*milvuspb.PrivilegeGroupInfo,error)) *RootCoordCatalog_GetPrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + +// DropPrivilegeGroup provides a mock function with given fields: ctx, groupName, privileges +func (_m *RootCoordCatalog) DropPrivilegeGroup(ctx context.Context, groupName string) error { + ret := _m.Called(ctx, groupName) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, groupName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RootCoordCatalog_DropPrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DropPrivilegeGroup' +type RootCoordCatalog_DropPrivilegeGroup_Call struct { + *mock.Call +} + +// DropPrivilegeGroup is a helper method to define mock.On call +// - ctx context.Context +// - groupName string +func (_e *RootCoordCatalog_Expecter) DropPrivilegeGroup(ctx interface{}, groupName interface{}) *RootCoordCatalog_DropPrivilegeGroup_Call { + return &RootCoordCatalog_DropPrivilegeGroup_Call{Call: _e.mock.On("DropPrivilegeGroup", ctx, groupName)} +} + +func (_c *RootCoordCatalog_DropPrivilegeGroup_Call) Run(run func(ctx context.Context, groupName string)) *RootCoordCatalog_DropPrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *RootCoordCatalog_DropPrivilegeGroup_Call) Return(_a0 error) *RootCoordCatalog_DropPrivilegeGroup_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *RootCoordCatalog_DropPrivilegeGroup_Call) RunAndReturn(run func(context.Context, string) error) *RootCoordCatalog_DropPrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + +// SavePrivilegeGroup provides a mock function with given fields: ctx, groupName, privileges +func (_m *RootCoordCatalog) SavePrivilegeGroup(ctx context.Context, data *milvuspb.PrivilegeGroupInfo) error { + ret := _m.Called(ctx, data) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.PrivilegeGroupInfo) error); ok { + r0 = rf(ctx, data) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RootCoordCatalog_SavePrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SavePrivilegeGroup' +type RootCoordCatalog_SavePrivilegeGroup_Call struct { + *mock.Call +} + +// SavePrivilegeGroup is a helper method to define mock.On call +// - ctx context.Context +// - groupName string +func (_e *RootCoordCatalog_Expecter) SavePrivilegeGroup(ctx interface{}, data interface{}) *RootCoordCatalog_SavePrivilegeGroup_Call { + return &RootCoordCatalog_SavePrivilegeGroup_Call{Call: _e.mock.On("SavePrivilegeGroup", ctx, data)} +} + +func (_c *RootCoordCatalog_SavePrivilegeGroup_Call) Run(run func(ctx context.Context, data *milvuspb.PrivilegeGroupInfo)) *RootCoordCatalog_SavePrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.PrivilegeGroupInfo)) + }) + return _c +} + +func (_c *RootCoordCatalog_SavePrivilegeGroup_Call) Return(_a0 error) *RootCoordCatalog_SavePrivilegeGroup_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *RootCoordCatalog_SavePrivilegeGroup_Call) RunAndReturn(run func(context.Context, *milvuspb.PrivilegeGroupInfo) error) *RootCoordCatalog_SavePrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + +// ListPrivilegeGroups provides a mock function with given fields: ctx +func (_m *RootCoordCatalog) ListPrivilegeGroups(ctx context.Context) ([]*milvuspb.PrivilegeGroupInfo, error) { + ret := _m.Called(ctx) + + var r0 []*milvuspb.PrivilegeGroupInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]*milvuspb.PrivilegeGroupInfo, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []*milvuspb.PrivilegeGroupInfo); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*milvuspb.PrivilegeGroupInfo) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RootCoordCatalog_ListPrivilegeGroups_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPrivilegeGroups' +type RootCoordCatalog_ListPrivilegeGroups_Call struct { + *mock.Call +} + +// ListPrivilegeGroups is a helper method to define mock.On call +// - ctx context.Context +func (_e *RootCoordCatalog_Expecter) ListPrivilegeGroups(ctx interface{}) *RootCoordCatalog_ListPrivilegeGroups_Call { + return &RootCoordCatalog_ListPrivilegeGroups_Call{Call: _e.mock.On("ListPrivilegeGroups", ctx)} +} + +func (_c *RootCoordCatalog_ListPrivilegeGroups_Call) Run(run func(ctx context.Context)) *RootCoordCatalog_ListPrivilegeGroups_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *RootCoordCatalog_ListPrivilegeGroups_Call) Return(_a0 []*milvuspb.PrivilegeGroupInfo, _a1 error) *RootCoordCatalog_ListPrivilegeGroups_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RootCoordCatalog_ListPrivilegeGroups_Call) RunAndReturn(run func(context.Context) ([]*milvuspb.PrivilegeGroupInfo, error)) *RootCoordCatalog_ListPrivilegeGroups_Call { + _c.Call.Return(run) + return _c +} diff --git a/internal/mocks/mock_proxy.go b/internal/mocks/mock_proxy.go index 36555c92ef429..2fc1856e907be 100644 --- a/internal/mocks/mock_proxy.go +++ b/internal/mocks/mock_proxy.go @@ -859,6 +859,61 @@ func (_c *MockProxy_CreatePartition_Call) RunAndReturn(run func(context.Context, return _c } +// CreatePrivilegeGroup provides a mock function with given fields: _a0, _a1 +func (_m *MockProxy) CreatePrivilegeGroup(_a0 context.Context, _a1 *milvuspb.CreatePrivilegeGroupRequest) (*commonpb.Status, error) { + ret := _m.Called(_a0, _a1) + + var r0 *commonpb.Status + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePrivilegeGroupRequest) (*commonpb.Status, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePrivilegeGroupRequest) *commonpb.Status); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*commonpb.Status) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.CreatePrivilegeGroupRequest) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockProxy_CreatePrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatePrivilegeGroup' +type MockProxy_CreatePrivilegeGroup_Call struct { + *mock.Call +} + +// CreatePrivilegeGroup is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *milvuspb.CreatePrivilegeGroupRequest +func (_e *MockProxy_Expecter) CreatePrivilegeGroup(_a0 interface{}, _a1 interface{}) *MockProxy_CreatePrivilegeGroup_Call { + return &MockProxy_CreatePrivilegeGroup_Call{Call: _e.mock.On("CreatePrivilegeGroup", _a0, _a1)} +} + +func (_c *MockProxy_CreatePrivilegeGroup_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.CreatePrivilegeGroupRequest)) *MockProxy_CreatePrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.CreatePrivilegeGroupRequest)) + }) + return _c +} + +func (_c *MockProxy_CreatePrivilegeGroup_Call) Return(_a0 *commonpb.Status, _a1 error) *MockProxy_CreatePrivilegeGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockProxy_CreatePrivilegeGroup_Call) RunAndReturn(run func(context.Context, *milvuspb.CreatePrivilegeGroupRequest) (*commonpb.Status, error)) *MockProxy_CreatePrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + // CreateResourceGroup provides a mock function with given fields: _a0, _a1 func (_m *MockProxy) CreateResourceGroup(_a0 context.Context, _a1 *milvuspb.CreateResourceGroupRequest) (*commonpb.Status, error) { ret := _m.Called(_a0, _a1) @@ -1684,6 +1739,61 @@ func (_c *MockProxy_DropPartition_Call) RunAndReturn(run func(context.Context, * return _c } +// DropPrivilegeGroup provides a mock function with given fields: _a0, _a1 +func (_m *MockProxy) DropPrivilegeGroup(_a0 context.Context, _a1 *milvuspb.DropPrivilegeGroupRequest) (*commonpb.Status, error) { + ret := _m.Called(_a0, _a1) + + var r0 *commonpb.Status + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.DropPrivilegeGroupRequest) (*commonpb.Status, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.DropPrivilegeGroupRequest) *commonpb.Status); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*commonpb.Status) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.DropPrivilegeGroupRequest) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockProxy_DropPrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DropPrivilegeGroup' +type MockProxy_DropPrivilegeGroup_Call struct { + *mock.Call +} + +// DropPrivilegeGroup is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *milvuspb.DropPrivilegeGroupRequest +func (_e *MockProxy_Expecter) DropPrivilegeGroup(_a0 interface{}, _a1 interface{}) *MockProxy_DropPrivilegeGroup_Call { + return &MockProxy_DropPrivilegeGroup_Call{Call: _e.mock.On("DropPrivilegeGroup", _a0, _a1)} +} + +func (_c *MockProxy_DropPrivilegeGroup_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.DropPrivilegeGroupRequest)) *MockProxy_DropPrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.DropPrivilegeGroupRequest)) + }) + return _c +} + +func (_c *MockProxy_DropPrivilegeGroup_Call) Return(_a0 *commonpb.Status, _a1 error) *MockProxy_DropPrivilegeGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockProxy_DropPrivilegeGroup_Call) RunAndReturn(run func(context.Context, *milvuspb.DropPrivilegeGroupRequest) (*commonpb.Status, error)) *MockProxy_DropPrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + // DropResourceGroup provides a mock function with given fields: _a0, _a1 func (_m *MockProxy) DropResourceGroup(_a0 context.Context, _a1 *milvuspb.DropResourceGroupRequest) (*commonpb.Status, error) { ret := _m.Called(_a0, _a1) @@ -4184,6 +4294,61 @@ func (_c *MockProxy_ListIndexedSegment_Call) RunAndReturn(run func(context.Conte return _c } +// ListPrivilegeGroups provides a mock function with given fields: _a0, _a1 +func (_m *MockProxy) ListPrivilegeGroups(_a0 context.Context, _a1 *milvuspb.ListPrivilegeGroupsRequest) (*milvuspb.ListPrivilegeGroupsResponse, error) { + ret := _m.Called(_a0, _a1) + + var r0 *milvuspb.ListPrivilegeGroupsResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.ListPrivilegeGroupsRequest) (*milvuspb.ListPrivilegeGroupsResponse, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.ListPrivilegeGroupsRequest) *milvuspb.ListPrivilegeGroupsResponse); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*milvuspb.ListPrivilegeGroupsResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.ListPrivilegeGroupsRequest) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockProxy_ListPrivilegeGroups_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPrivilegeGroups' +type MockProxy_ListPrivilegeGroups_Call struct { + *mock.Call +} + +// ListPrivilegeGroups is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *milvuspb.ListPrivilegeGroupsRequest +func (_e *MockProxy_Expecter) ListPrivilegeGroups(_a0 interface{}, _a1 interface{}) *MockProxy_ListPrivilegeGroups_Call { + return &MockProxy_ListPrivilegeGroups_Call{Call: _e.mock.On("ListPrivilegeGroups", _a0, _a1)} +} + +func (_c *MockProxy_ListPrivilegeGroups_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.ListPrivilegeGroupsRequest)) *MockProxy_ListPrivilegeGroups_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.ListPrivilegeGroupsRequest)) + }) + return _c +} + +func (_c *MockProxy_ListPrivilegeGroups_Call) Return(_a0 *milvuspb.ListPrivilegeGroupsResponse, _a1 error) *MockProxy_ListPrivilegeGroups_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockProxy_ListPrivilegeGroups_Call) RunAndReturn(run func(context.Context, *milvuspb.ListPrivilegeGroupsRequest) (*milvuspb.ListPrivilegeGroupsResponse, error)) *MockProxy_ListPrivilegeGroups_Call { + _c.Call.Return(run) + return _c +} + // ListResourceGroups provides a mock function with given fields: _a0, _a1 func (_m *MockProxy) ListResourceGroups(_a0 context.Context, _a1 *milvuspb.ListResourceGroupsRequest) (*milvuspb.ListResourceGroupsResponse, error) { ret := _m.Called(_a0, _a1) @@ -4514,6 +4679,61 @@ func (_c *MockProxy_OperatePrivilege_Call) RunAndReturn(run func(context.Context return _c } +// OperatePrivilegeGroup provides a mock function with given fields: _a0, _a1 +func (_m *MockProxy) OperatePrivilegeGroup(_a0 context.Context, _a1 *milvuspb.OperatePrivilegeGroupRequest) (*commonpb.Status, error) { + ret := _m.Called(_a0, _a1) + + var r0 *commonpb.Status + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.OperatePrivilegeGroupRequest) (*commonpb.Status, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.OperatePrivilegeGroupRequest) *commonpb.Status); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*commonpb.Status) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.OperatePrivilegeGroupRequest) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockProxy_OperatePrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OperatePrivilegeGroup' +type MockProxy_OperatePrivilegeGroup_Call struct { + *mock.Call +} + +// OperatePrivilegeGroup is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *milvuspb.OperatePrivilegeGroupRequest +func (_e *MockProxy_Expecter) OperatePrivilegeGroup(_a0 interface{}, _a1 interface{}) *MockProxy_OperatePrivilegeGroup_Call { + return &MockProxy_OperatePrivilegeGroup_Call{Call: _e.mock.On("OperatePrivilegeGroup", _a0, _a1)} +} + +func (_c *MockProxy_OperatePrivilegeGroup_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.OperatePrivilegeGroupRequest)) *MockProxy_OperatePrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.OperatePrivilegeGroupRequest)) + }) + return _c +} + +func (_c *MockProxy_OperatePrivilegeGroup_Call) Return(_a0 *commonpb.Status, _a1 error) *MockProxy_OperatePrivilegeGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockProxy_OperatePrivilegeGroup_Call) RunAndReturn(run func(context.Context, *milvuspb.OperatePrivilegeGroupRequest) (*commonpb.Status, error)) *MockProxy_OperatePrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + // OperateUserRole provides a mock function with given fields: _a0, _a1 func (_m *MockProxy) OperateUserRole(_a0 context.Context, _a1 *milvuspb.OperateUserRoleRequest) (*commonpb.Status, error) { ret := _m.Called(_a0, _a1) diff --git a/internal/mocks/mock_querynode.go b/internal/mocks/mock_querynode.go index ccb27fa884e8d..abcf83fade8d0 100644 --- a/internal/mocks/mock_querynode.go +++ b/internal/mocks/mock_querynode.go @@ -89,10 +89,6 @@ func (_c *MockQueryNode_Delete_Call) RunAndReturn(run func(context.Context, *que func (_m *MockQueryNode) DeleteBatch(_a0 context.Context, _a1 *querypb.DeleteBatchRequest) (*querypb.DeleteBatchResponse, error) { ret := _m.Called(_a0, _a1) - if len(ret) == 0 { - panic("no return value specified for DeleteBatch") - } - var r0 *querypb.DeleteBatchResponse var r1 error if rf, ok := ret.Get(0).(func(context.Context, *querypb.DeleteBatchRequest) (*querypb.DeleteBatchResponse, error)); ok { diff --git a/internal/mocks/mock_querynode_client.go b/internal/mocks/mock_querynode_client.go index 10e179518285a..e7777eb6ab7c6 100644 --- a/internal/mocks/mock_querynode_client.go +++ b/internal/mocks/mock_querynode_client.go @@ -153,10 +153,6 @@ func (_m *MockQueryNodeClient) DeleteBatch(ctx context.Context, in *querypb.Dele _ca = append(_ca, _va...) ret := _m.Called(_ca...) - if len(ret) == 0 { - panic("no return value specified for DeleteBatch") - } - var r0 *querypb.DeleteBatchResponse var r1 error if rf, ok := ret.Get(0).(func(context.Context, *querypb.DeleteBatchRequest, ...grpc.CallOption) (*querypb.DeleteBatchResponse, error)); ok { diff --git a/internal/mocks/mock_rootcoord.go b/internal/mocks/mock_rootcoord.go index 00c997d90e9e0..877174804472f 100644 --- a/internal/mocks/mock_rootcoord.go +++ b/internal/mocks/mock_rootcoord.go @@ -696,6 +696,61 @@ func (_c *RootCoord_CreatePartition_Call) RunAndReturn(run func(context.Context, return _c } +// CreatePrivilegeGroup provides a mock function with given fields: _a0, _a1 +func (_m *RootCoord) CreatePrivilegeGroup(_a0 context.Context, _a1 *milvuspb.CreatePrivilegeGroupRequest) (*commonpb.Status, error) { + ret := _m.Called(_a0, _a1) + + var r0 *commonpb.Status + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePrivilegeGroupRequest) (*commonpb.Status, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePrivilegeGroupRequest) *commonpb.Status); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*commonpb.Status) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.CreatePrivilegeGroupRequest) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RootCoord_CreatePrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatePrivilegeGroup' +type RootCoord_CreatePrivilegeGroup_Call struct { + *mock.Call +} + +// CreatePrivilegeGroup is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *milvuspb.CreatePrivilegeGroupRequest +func (_e *RootCoord_Expecter) CreatePrivilegeGroup(_a0 interface{}, _a1 interface{}) *RootCoord_CreatePrivilegeGroup_Call { + return &RootCoord_CreatePrivilegeGroup_Call{Call: _e.mock.On("CreatePrivilegeGroup", _a0, _a1)} +} + +func (_c *RootCoord_CreatePrivilegeGroup_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.CreatePrivilegeGroupRequest)) *RootCoord_CreatePrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.CreatePrivilegeGroupRequest)) + }) + return _c +} + +func (_c *RootCoord_CreatePrivilegeGroup_Call) Return(_a0 *commonpb.Status, _a1 error) *RootCoord_CreatePrivilegeGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RootCoord_CreatePrivilegeGroup_Call) RunAndReturn(run func(context.Context, *milvuspb.CreatePrivilegeGroupRequest) (*commonpb.Status, error)) *RootCoord_CreatePrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + // CreateRole provides a mock function with given fields: _a0, _a1 func (_m *RootCoord) CreateRole(_a0 context.Context, _a1 *milvuspb.CreateRoleRequest) (*commonpb.Status, error) { ret := _m.Called(_a0, _a1) @@ -1246,6 +1301,61 @@ func (_c *RootCoord_DropPartition_Call) RunAndReturn(run func(context.Context, * return _c } +// DropPrivilegeGroup provides a mock function with given fields: _a0, _a1 +func (_m *RootCoord) DropPrivilegeGroup(_a0 context.Context, _a1 *milvuspb.DropPrivilegeGroupRequest) (*commonpb.Status, error) { + ret := _m.Called(_a0, _a1) + + var r0 *commonpb.Status + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.DropPrivilegeGroupRequest) (*commonpb.Status, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.DropPrivilegeGroupRequest) *commonpb.Status); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*commonpb.Status) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.DropPrivilegeGroupRequest) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RootCoord_DropPrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DropPrivilegeGroup' +type RootCoord_DropPrivilegeGroup_Call struct { + *mock.Call +} + +// DropPrivilegeGroup is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *milvuspb.DropPrivilegeGroupRequest +func (_e *RootCoord_Expecter) DropPrivilegeGroup(_a0 interface{}, _a1 interface{}) *RootCoord_DropPrivilegeGroup_Call { + return &RootCoord_DropPrivilegeGroup_Call{Call: _e.mock.On("DropPrivilegeGroup", _a0, _a1)} +} + +func (_c *RootCoord_DropPrivilegeGroup_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.DropPrivilegeGroupRequest)) *RootCoord_DropPrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.DropPrivilegeGroupRequest)) + }) + return _c +} + +func (_c *RootCoord_DropPrivilegeGroup_Call) Return(_a0 *commonpb.Status, _a1 error) *RootCoord_DropPrivilegeGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RootCoord_DropPrivilegeGroup_Call) RunAndReturn(run func(context.Context, *milvuspb.DropPrivilegeGroupRequest) (*commonpb.Status, error)) *RootCoord_DropPrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + // DropRole provides a mock function with given fields: _a0, _a1 func (_m *RootCoord) DropRole(_a0 context.Context, _a1 *milvuspb.DropRoleRequest) (*commonpb.Status, error) { ret := _m.Called(_a0, _a1) @@ -2002,6 +2112,61 @@ func (_c *RootCoord_ListPolicy_Call) RunAndReturn(run func(context.Context, *int return _c } +// ListPrivilegeGroups provides a mock function with given fields: _a0, _a1 +func (_m *RootCoord) ListPrivilegeGroups(_a0 context.Context, _a1 *milvuspb.ListPrivilegeGroupsRequest) (*milvuspb.ListPrivilegeGroupsResponse, error) { + ret := _m.Called(_a0, _a1) + + var r0 *milvuspb.ListPrivilegeGroupsResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.ListPrivilegeGroupsRequest) (*milvuspb.ListPrivilegeGroupsResponse, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.ListPrivilegeGroupsRequest) *milvuspb.ListPrivilegeGroupsResponse); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*milvuspb.ListPrivilegeGroupsResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.ListPrivilegeGroupsRequest) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RootCoord_ListPrivilegeGroups_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPrivilegeGroups' +type RootCoord_ListPrivilegeGroups_Call struct { + *mock.Call +} + +// ListPrivilegeGroups is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *milvuspb.ListPrivilegeGroupsRequest +func (_e *RootCoord_Expecter) ListPrivilegeGroups(_a0 interface{}, _a1 interface{}) *RootCoord_ListPrivilegeGroups_Call { + return &RootCoord_ListPrivilegeGroups_Call{Call: _e.mock.On("ListPrivilegeGroups", _a0, _a1)} +} + +func (_c *RootCoord_ListPrivilegeGroups_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.ListPrivilegeGroupsRequest)) *RootCoord_ListPrivilegeGroups_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.ListPrivilegeGroupsRequest)) + }) + return _c +} + +func (_c *RootCoord_ListPrivilegeGroups_Call) Return(_a0 *milvuspb.ListPrivilegeGroupsResponse, _a1 error) *RootCoord_ListPrivilegeGroups_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RootCoord_ListPrivilegeGroups_Call) RunAndReturn(run func(context.Context, *milvuspb.ListPrivilegeGroupsRequest) (*milvuspb.ListPrivilegeGroupsResponse, error)) *RootCoord_ListPrivilegeGroups_Call { + _c.Call.Return(run) + return _c +} + // OperatePrivilege provides a mock function with given fields: _a0, _a1 func (_m *RootCoord) OperatePrivilege(_a0 context.Context, _a1 *milvuspb.OperatePrivilegeRequest) (*commonpb.Status, error) { ret := _m.Called(_a0, _a1) @@ -2057,6 +2222,61 @@ func (_c *RootCoord_OperatePrivilege_Call) RunAndReturn(run func(context.Context return _c } +// OperatePrivilegeGroup provides a mock function with given fields: _a0, _a1 +func (_m *RootCoord) OperatePrivilegeGroup(_a0 context.Context, _a1 *milvuspb.OperatePrivilegeGroupRequest) (*commonpb.Status, error) { + ret := _m.Called(_a0, _a1) + + var r0 *commonpb.Status + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.OperatePrivilegeGroupRequest) (*commonpb.Status, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.OperatePrivilegeGroupRequest) *commonpb.Status); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*commonpb.Status) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.OperatePrivilegeGroupRequest) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RootCoord_OperatePrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OperatePrivilegeGroup' +type RootCoord_OperatePrivilegeGroup_Call struct { + *mock.Call +} + +// OperatePrivilegeGroup is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *milvuspb.OperatePrivilegeGroupRequest +func (_e *RootCoord_Expecter) OperatePrivilegeGroup(_a0 interface{}, _a1 interface{}) *RootCoord_OperatePrivilegeGroup_Call { + return &RootCoord_OperatePrivilegeGroup_Call{Call: _e.mock.On("OperatePrivilegeGroup", _a0, _a1)} +} + +func (_c *RootCoord_OperatePrivilegeGroup_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.OperatePrivilegeGroupRequest)) *RootCoord_OperatePrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.OperatePrivilegeGroupRequest)) + }) + return _c +} + +func (_c *RootCoord_OperatePrivilegeGroup_Call) Return(_a0 *commonpb.Status, _a1 error) *RootCoord_OperatePrivilegeGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RootCoord_OperatePrivilegeGroup_Call) RunAndReturn(run func(context.Context, *milvuspb.OperatePrivilegeGroupRequest) (*commonpb.Status, error)) *RootCoord_OperatePrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + // OperateUserRole provides a mock function with given fields: _a0, _a1 func (_m *RootCoord) OperateUserRole(_a0 context.Context, _a1 *milvuspb.OperateUserRoleRequest) (*commonpb.Status, error) { ret := _m.Called(_a0, _a1) diff --git a/internal/mocks/mock_rootcoord_client.go b/internal/mocks/mock_rootcoord_client.go index dbb566bc3d4c3..3d3db95c6963d 100644 --- a/internal/mocks/mock_rootcoord_client.go +++ b/internal/mocks/mock_rootcoord_client.go @@ -914,6 +914,76 @@ func (_c *MockRootCoordClient_CreatePartition_Call) RunAndReturn(run func(contex return _c } +// CreatePrivilegeGroup provides a mock function with given fields: ctx, in, opts +func (_m *MockRootCoordClient) CreatePrivilegeGroup(ctx context.Context, in *milvuspb.CreatePrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *commonpb.Status + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePrivilegeGroupRequest, ...grpc.CallOption) (*commonpb.Status, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePrivilegeGroupRequest, ...grpc.CallOption) *commonpb.Status); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*commonpb.Status) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.CreatePrivilegeGroupRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockRootCoordClient_CreatePrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatePrivilegeGroup' +type MockRootCoordClient_CreatePrivilegeGroup_Call struct { + *mock.Call +} + +// CreatePrivilegeGroup is a helper method to define mock.On call +// - ctx context.Context +// - in *milvuspb.CreatePrivilegeGroupRequest +// - opts ...grpc.CallOption +func (_e *MockRootCoordClient_Expecter) CreatePrivilegeGroup(ctx interface{}, in interface{}, opts ...interface{}) *MockRootCoordClient_CreatePrivilegeGroup_Call { + return &MockRootCoordClient_CreatePrivilegeGroup_Call{Call: _e.mock.On("CreatePrivilegeGroup", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *MockRootCoordClient_CreatePrivilegeGroup_Call) Run(run func(ctx context.Context, in *milvuspb.CreatePrivilegeGroupRequest, opts ...grpc.CallOption)) *MockRootCoordClient_CreatePrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*milvuspb.CreatePrivilegeGroupRequest), variadicArgs...) + }) + return _c +} + +func (_c *MockRootCoordClient_CreatePrivilegeGroup_Call) Return(_a0 *commonpb.Status, _a1 error) *MockRootCoordClient_CreatePrivilegeGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockRootCoordClient_CreatePrivilegeGroup_Call) RunAndReturn(run func(context.Context, *milvuspb.CreatePrivilegeGroupRequest, ...grpc.CallOption) (*commonpb.Status, error)) *MockRootCoordClient_CreatePrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + // CreateRole provides a mock function with given fields: ctx, in, opts func (_m *MockRootCoordClient) CreateRole(ctx context.Context, in *milvuspb.CreateRoleRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { _va := make([]interface{}, len(opts)) @@ -1614,6 +1684,76 @@ func (_c *MockRootCoordClient_DropPartition_Call) RunAndReturn(run func(context. return _c } +// DropPrivilegeGroup provides a mock function with given fields: ctx, in, opts +func (_m *MockRootCoordClient) DropPrivilegeGroup(ctx context.Context, in *milvuspb.DropPrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *commonpb.Status + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.DropPrivilegeGroupRequest, ...grpc.CallOption) (*commonpb.Status, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.DropPrivilegeGroupRequest, ...grpc.CallOption) *commonpb.Status); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*commonpb.Status) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.DropPrivilegeGroupRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockRootCoordClient_DropPrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DropPrivilegeGroup' +type MockRootCoordClient_DropPrivilegeGroup_Call struct { + *mock.Call +} + +// DropPrivilegeGroup is a helper method to define mock.On call +// - ctx context.Context +// - in *milvuspb.DropPrivilegeGroupRequest +// - opts ...grpc.CallOption +func (_e *MockRootCoordClient_Expecter) DropPrivilegeGroup(ctx interface{}, in interface{}, opts ...interface{}) *MockRootCoordClient_DropPrivilegeGroup_Call { + return &MockRootCoordClient_DropPrivilegeGroup_Call{Call: _e.mock.On("DropPrivilegeGroup", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *MockRootCoordClient_DropPrivilegeGroup_Call) Run(run func(ctx context.Context, in *milvuspb.DropPrivilegeGroupRequest, opts ...grpc.CallOption)) *MockRootCoordClient_DropPrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*milvuspb.DropPrivilegeGroupRequest), variadicArgs...) + }) + return _c +} + +func (_c *MockRootCoordClient_DropPrivilegeGroup_Call) Return(_a0 *commonpb.Status, _a1 error) *MockRootCoordClient_DropPrivilegeGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockRootCoordClient_DropPrivilegeGroup_Call) RunAndReturn(run func(context.Context, *milvuspb.DropPrivilegeGroupRequest, ...grpc.CallOption) (*commonpb.Status, error)) *MockRootCoordClient_DropPrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + // DropRole provides a mock function with given fields: ctx, in, opts func (_m *MockRootCoordClient) DropRole(ctx context.Context, in *milvuspb.DropRoleRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { _va := make([]interface{}, len(opts)) @@ -2524,6 +2664,76 @@ func (_c *MockRootCoordClient_ListPolicy_Call) RunAndReturn(run func(context.Con return _c } +// ListPrivilegeGroups provides a mock function with given fields: ctx, in, opts +func (_m *MockRootCoordClient) ListPrivilegeGroups(ctx context.Context, in *milvuspb.ListPrivilegeGroupsRequest, opts ...grpc.CallOption) (*milvuspb.ListPrivilegeGroupsResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *milvuspb.ListPrivilegeGroupsResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.ListPrivilegeGroupsRequest, ...grpc.CallOption) (*milvuspb.ListPrivilegeGroupsResponse, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.ListPrivilegeGroupsRequest, ...grpc.CallOption) *milvuspb.ListPrivilegeGroupsResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*milvuspb.ListPrivilegeGroupsResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.ListPrivilegeGroupsRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockRootCoordClient_ListPrivilegeGroups_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPrivilegeGroups' +type MockRootCoordClient_ListPrivilegeGroups_Call struct { + *mock.Call +} + +// ListPrivilegeGroups is a helper method to define mock.On call +// - ctx context.Context +// - in *milvuspb.ListPrivilegeGroupsRequest +// - opts ...grpc.CallOption +func (_e *MockRootCoordClient_Expecter) ListPrivilegeGroups(ctx interface{}, in interface{}, opts ...interface{}) *MockRootCoordClient_ListPrivilegeGroups_Call { + return &MockRootCoordClient_ListPrivilegeGroups_Call{Call: _e.mock.On("ListPrivilegeGroups", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *MockRootCoordClient_ListPrivilegeGroups_Call) Run(run func(ctx context.Context, in *milvuspb.ListPrivilegeGroupsRequest, opts ...grpc.CallOption)) *MockRootCoordClient_ListPrivilegeGroups_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*milvuspb.ListPrivilegeGroupsRequest), variadicArgs...) + }) + return _c +} + +func (_c *MockRootCoordClient_ListPrivilegeGroups_Call) Return(_a0 *milvuspb.ListPrivilegeGroupsResponse, _a1 error) *MockRootCoordClient_ListPrivilegeGroups_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockRootCoordClient_ListPrivilegeGroups_Call) RunAndReturn(run func(context.Context, *milvuspb.ListPrivilegeGroupsRequest, ...grpc.CallOption) (*milvuspb.ListPrivilegeGroupsResponse, error)) *MockRootCoordClient_ListPrivilegeGroups_Call { + _c.Call.Return(run) + return _c +} + // OperatePrivilege provides a mock function with given fields: ctx, in, opts func (_m *MockRootCoordClient) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivilegeRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { _va := make([]interface{}, len(opts)) @@ -2594,6 +2804,76 @@ func (_c *MockRootCoordClient_OperatePrivilege_Call) RunAndReturn(run func(conte return _c } +// OperatePrivilegeGroup provides a mock function with given fields: ctx, in, opts +func (_m *MockRootCoordClient) OperatePrivilegeGroup(ctx context.Context, in *milvuspb.OperatePrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *commonpb.Status + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.OperatePrivilegeGroupRequest, ...grpc.CallOption) (*commonpb.Status, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.OperatePrivilegeGroupRequest, ...grpc.CallOption) *commonpb.Status); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*commonpb.Status) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.OperatePrivilegeGroupRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockRootCoordClient_OperatePrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OperatePrivilegeGroup' +type MockRootCoordClient_OperatePrivilegeGroup_Call struct { + *mock.Call +} + +// OperatePrivilegeGroup is a helper method to define mock.On call +// - ctx context.Context +// - in *milvuspb.OperatePrivilegeGroupRequest +// - opts ...grpc.CallOption +func (_e *MockRootCoordClient_Expecter) OperatePrivilegeGroup(ctx interface{}, in interface{}, opts ...interface{}) *MockRootCoordClient_OperatePrivilegeGroup_Call { + return &MockRootCoordClient_OperatePrivilegeGroup_Call{Call: _e.mock.On("OperatePrivilegeGroup", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *MockRootCoordClient_OperatePrivilegeGroup_Call) Run(run func(ctx context.Context, in *milvuspb.OperatePrivilegeGroupRequest, opts ...grpc.CallOption)) *MockRootCoordClient_OperatePrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*milvuspb.OperatePrivilegeGroupRequest), variadicArgs...) + }) + return _c +} + +func (_c *MockRootCoordClient_OperatePrivilegeGroup_Call) Return(_a0 *commonpb.Status, _a1 error) *MockRootCoordClient_OperatePrivilegeGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockRootCoordClient_OperatePrivilegeGroup_Call) RunAndReturn(run func(context.Context, *milvuspb.OperatePrivilegeGroupRequest, ...grpc.CallOption) (*commonpb.Status, error)) *MockRootCoordClient_OperatePrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + // OperateUserRole provides a mock function with given fields: ctx, in, opts func (_m *MockRootCoordClient) OperateUserRole(ctx context.Context, in *milvuspb.OperateUserRoleRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { _va := make([]interface{}, len(opts)) diff --git a/internal/proto/internal.proto b/internal/proto/internal.proto index 86bc546523841..7db4bd9a7c265 100644 --- a/internal/proto/internal.proto +++ b/internal/proto/internal.proto @@ -4,6 +4,7 @@ option go_package = "github.com/milvus-io/milvus/internal/proto/internalpb"; import "common.proto"; import "schema.proto"; +import "milvus.proto"; message GetTimeTickChannelRequest { } @@ -255,6 +256,7 @@ message ListPolicyResponse { common.Status status = 1; repeated string policy_infos = 2; repeated string user_roles = 3; + repeated milvus.PrivilegeGroupInfo privilege_groups = 4; } message ShowConfigurationsRequest { diff --git a/internal/proto/root_coord.proto b/internal/proto/root_coord.proto index a1408f6e29656..bc1aadc6d6525 100644 --- a/internal/proto/root_coord.proto +++ b/internal/proto/root_coord.proto @@ -134,6 +134,10 @@ service RootCoord { rpc ListPolicy(internal.ListPolicyRequest) returns (internal.ListPolicyResponse) {} rpc BackupRBAC(milvus.BackupRBACMetaRequest) returns (milvus.BackupRBACMetaResponse){} rpc RestoreRBAC(milvus.RestoreRBACMetaRequest) returns (common.Status){} + rpc CreatePrivilegeGroup(milvus.CreatePrivilegeGroupRequest) returns (common.Status) {} + rpc DropPrivilegeGroup(milvus.DropPrivilegeGroupRequest) returns (common.Status) {} + rpc ListPrivilegeGroups(milvus.ListPrivilegeGroupsRequest) returns (milvus.ListPrivilegeGroupsResponse) {} + rpc OperatePrivilegeGroup(milvus.OperatePrivilegeGroupRequest) returns (common.Status) {} rpc CheckHealth(milvus.CheckHealthRequest) returns (milvus.CheckHealthResponse) {} diff --git a/internal/proxy/impl.go b/internal/proxy/impl.go index b186ffe91c52c..008a7ce98d83c 100644 --- a/internal/proxy/impl.go +++ b/internal/proxy/impl.go @@ -6388,3 +6388,124 @@ func DeregisterSubLabel(subLabel string) { rateCol.DeregisterSubLabel(internalpb.RateType_DQLQuery.String(), subLabel) rateCol.DeregisterSubLabel(internalpb.RateType_DQLSearch.String(), subLabel) } + +func (node *Proxy) CreatePrivilegeGroup(ctx context.Context, req *milvuspb.CreatePrivilegeGroupRequest) (*commonpb.Status, error) { + ctx, sp := otel.Tracer(typeutil.ProxyRole).Start(ctx, "Proxy-CreatePrivilegeGroup") + defer sp.End() + + log := log.Ctx(ctx) + + log.Info("CreatePrivilegeGroup", zap.Any("req", req)) + if err := merr.CheckHealthy(node.GetStateCode()); err != nil { + return merr.Status(err), nil + } + if req.GroupName == "" { + return merr.Status(fmt.Errorf("the group name in the drop privilege group request is nil")), nil + } + if req.Base == nil { + req.Base = &commonpb.MsgBase{} + } + req.Base.MsgType = commonpb.MsgType_CreatePrivilegeGroup + + result, err := node.rootCoord.CreatePrivilegeGroup(ctx, req) + if err != nil { + log.Warn("fail to create privilege group", zap.Error(err)) + return merr.Status(err), nil + } + if merr.Ok(result) { + SendReplicateMessagePack(ctx, node.replicateMsgStream, req) + } + return result, nil +} + +func (node *Proxy) DropPrivilegeGroup(ctx context.Context, req *milvuspb.DropPrivilegeGroupRequest) (*commonpb.Status, error) { + ctx, sp := otel.Tracer(typeutil.ProxyRole).Start(ctx, "Proxy-DropPrivilegeGroup") + defer sp.End() + + log := log.Ctx(ctx) + + log.Info("DropPrivilegeGroup", zap.Any("req", req)) + if err := merr.CheckHealthy(node.GetStateCode()); err != nil { + return merr.Status(err), nil + } + if req.GroupName == "" { + return merr.Status(fmt.Errorf("the group name in the drop privilege group request is nil")), nil + } + if req.Base == nil { + req.Base = &commonpb.MsgBase{} + } + req.Base.MsgType = commonpb.MsgType_DropPrivilegeGroup + + result, err := node.rootCoord.DropPrivilegeGroup(ctx, req) + if err != nil { + log.Warn("fail to drop privilege group", zap.Error(err)) + return merr.Status(err), nil + } + if merr.Ok(result) { + SendReplicateMessagePack(ctx, node.replicateMsgStream, req) + } + return result, nil +} + +func (node *Proxy) ListPrivilegeGroups(ctx context.Context, req *milvuspb.ListPrivilegeGroupsRequest) (*milvuspb.ListPrivilegeGroupsResponse, error) { + ctx, sp := otel.Tracer(typeutil.ProxyRole).Start(ctx, "Proxy-ListPrivilegeGroups") + defer sp.End() + + log := log.Ctx(ctx).With( + zap.String("role", typeutil.ProxyRole)) + + log.Debug("ListPrivilegeGroups") + if err := merr.CheckHealthy(node.GetStateCode()); err != nil { + return &milvuspb.ListPrivilegeGroupsResponse{Status: merr.Status(err)}, nil + } + if req.Base == nil { + req.Base = &commonpb.MsgBase{} + } + req.Base.MsgType = commonpb.MsgType_ListPrivilegeGroups + rootCoordReq := &milvuspb.ListPrivilegeGroupsRequest{ + Base: commonpbutil.NewMsgBase( + commonpbutil.WithMsgType(commonpb.MsgType_ListPrivilegeGroups), + ), + } + resp, err := node.rootCoord.ListPrivilegeGroups(ctx, rootCoordReq) + if err != nil { + return &milvuspb.ListPrivilegeGroupsResponse{ + Status: merr.Status(err), + }, nil + } + return resp, nil +} + +func (node *Proxy) OperatePrivilegeGroup(ctx context.Context, req *milvuspb.OperatePrivilegeGroupRequest) (*commonpb.Status, error) { + ctx, sp := otel.Tracer(typeutil.ProxyRole).Start(ctx, "Proxy-OperatePrivilegeGroup") + defer sp.End() + + log := log.Ctx(ctx) + + log.Info("OperatePrivilegeGroup", zap.Any("req", req)) + if err := merr.CheckHealthy(node.GetStateCode()); err != nil { + return merr.Status(err), nil + } + if req.GroupName == "" { + return merr.Status(fmt.Errorf("the group name in the drop privilege group request is nil")), nil + } + for _, priv := range req.GetPrivileges() { + if err := ValidatePrivilege(priv.Name); err != nil { + return merr.Status(err), nil + } + } + if req.Base == nil { + req.Base = &commonpb.MsgBase{} + } + req.Base.MsgType = commonpb.MsgType_OperatePrivilegeGroup + + result, err := node.rootCoord.OperatePrivilegeGroup(ctx, req) + if err != nil { + log.Warn("fail to operate privilege group", zap.Error(err)) + return merr.Status(err), nil + } + if merr.Ok(result) { + SendReplicateMessagePack(ctx, node.replicateMsgStream, req) + } + return result, nil +} diff --git a/internal/proxy/meta_cache.go b/internal/proxy/meta_cache.go index 870b6ce12df02..16b16c3ce10c0 100644 --- a/internal/proxy/meta_cache.go +++ b/internal/proxy/meta_cache.go @@ -1120,9 +1120,15 @@ func (m *MetaCache) RefreshPolicyInfo(op typeutil.CacheOp) (err error) { switch op.OpType { case typeutil.CacheGrantPrivilege: - m.privilegeInfos[op.OpKey] = struct{}{} + keys := funcutil.PrivilegesForPolicy(op.OpKey) + for _, key := range keys { + m.privilegeInfos[key] = struct{}{} + } case typeutil.CacheRevokePrivilege: - delete(m.privilegeInfos, op.OpKey) + keys := funcutil.PrivilegesForPolicy(op.OpKey) + for _, key := range keys { + delete(m.privilegeInfos, key) + } case typeutil.CacheAddUserToRole: user, role, err := funcutil.DecodeUserRoleCache(op.OpKey) if err != nil { diff --git a/internal/proxy/rootcoord_mock_test.go b/internal/proxy/rootcoord_mock_test.go index a7c8f96e24cad..1fb988cbf554c 100644 --- a/internal/proxy/rootcoord_mock_test.go +++ b/internal/proxy/rootcoord_mock_test.go @@ -1130,6 +1130,22 @@ func (coord *RootCoordMock) RestoreRBAC(ctx context.Context, in *milvuspb.Restor return &commonpb.Status{}, nil } +func (coord *RootCoordMock) CreatePrivilegeGroup(ctx context.Context, req *milvuspb.CreatePrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + return &commonpb.Status{}, nil +} + +func (coord *RootCoordMock) DropPrivilegeGroup(ctx context.Context, req *milvuspb.DropPrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + return &commonpb.Status{}, nil +} + +func (coord *RootCoordMock) ListPrivilegeGroups(ctx context.Context, req *milvuspb.ListPrivilegeGroupsRequest, opts ...grpc.CallOption) (*milvuspb.ListPrivilegeGroupsResponse, error) { + return &milvuspb.ListPrivilegeGroupsResponse{}, nil +} + +func (coord *RootCoordMock) OperatePrivilegeGroup(ctx context.Context, req *milvuspb.OperatePrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + return &commonpb.Status{}, nil +} + type DescribeCollectionFunc func(ctx context.Context, request *milvuspb.DescribeCollectionRequest, opts ...grpc.CallOption) (*milvuspb.DescribeCollectionResponse, error) type ShowPartitionsFunc func(ctx context.Context, request *milvuspb.ShowPartitionsRequest, opts ...grpc.CallOption) (*milvuspb.ShowPartitionsResponse, error) diff --git a/internal/proxy/util.go b/internal/proxy/util.go index dc048daeb3fb5..dae3c88c56fe4 100644 --- a/internal/proxy/util.go +++ b/internal/proxy/util.go @@ -906,14 +906,6 @@ func ValidateObjectType(entity string) error { return validateName(entity, "ObjectType") } -func ValidatePrincipalName(entity string) error { - return validateName(entity, "PrincipalName") -} - -func ValidatePrincipalType(entity string) error { - return validateName(entity, "PrincipalType") -} - func ValidatePrivilege(entity string) error { if util.IsAnyWord(entity) { return nil diff --git a/internal/proxy/util_test.go b/internal/proxy/util_test.go index f95a9299252c5..0aed56b3a4b09 100644 --- a/internal/proxy/util_test.go +++ b/internal/proxy/util_test.go @@ -804,8 +804,6 @@ func TestValidateName(t *testing.T) { assert.Nil(t, ValidateRoleName(name)) assert.Nil(t, ValidateObjectName(name)) assert.Nil(t, ValidateObjectType(name)) - assert.Nil(t, ValidatePrincipalName(name)) - assert.Nil(t, ValidatePrincipalType(name)) assert.Nil(t, ValidatePrivilege(name)) } @@ -828,8 +826,6 @@ func TestValidateName(t *testing.T) { assert.NotNil(t, validateName(name, nameType)) assert.NotNil(t, ValidateRoleName(name)) assert.NotNil(t, ValidateObjectType(name)) - assert.NotNil(t, ValidatePrincipalName(name)) - assert.NotNil(t, ValidatePrincipalType(name)) assert.NotNil(t, ValidatePrivilege(name)) } assert.NotNil(t, ValidateObjectName(" ")) diff --git a/internal/rootcoord/meta_table.go b/internal/rootcoord/meta_table.go index f86eb82297c66..31364f431b055 100644 --- a/internal/rootcoord/meta_table.go +++ b/internal/rootcoord/meta_table.go @@ -22,6 +22,7 @@ import ( "sync" "github.com/cockroachdb/errors" + "github.com/samber/lo" "go.uber.org/zap" "golang.org/x/exp/maps" @@ -96,6 +97,12 @@ type IMetaTable interface { ListUserRole(tenant string) ([]string, error) BackupRBAC(ctx context.Context, tenant string) (*milvuspb.RBACMeta, error) RestoreRBAC(ctx context.Context, tenant string, meta *milvuspb.RBACMeta) error + IsCustomPrivilegeGroup(groupName string) (bool, error) + CreatePrivilegeGroup(groupName string) error + DropPrivilegeGroup(groupName string) error + ListPrivilegeGroups() ([]*milvuspb.PrivilegeGroupInfo, error) + OperatePrivilegeGroup(groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error + GetPrivilegeGroupRoles(groupName string) ([]*milvuspb.RoleEntity, error) } type MetaTable struct { @@ -1425,3 +1432,187 @@ func (mt *MetaTable) RestoreRBAC(ctx context.Context, tenant string, meta *milvu return mt.catalog.RestoreRBAC(mt.ctx, tenant, meta) } + +// check if the privielge group name is defined by users +func (mt *MetaTable) IsCustomPrivilegeGroup(groupName string) (bool, error) { + privGroups, err := mt.catalog.ListPrivilegeGroups(mt.ctx) + if err != nil { + return false, err + } + for _, group := range privGroups { + if group.GroupName == groupName { + return true, nil + } + } + return false, nil +} + +func (mt *MetaTable) CreatePrivilegeGroup(groupName string) error { + if funcutil.IsEmptyString(groupName) { + return fmt.Errorf("the privilege group name is empty") + } + mt.permissionLock.Lock() + defer mt.permissionLock.Unlock() + + definedByUsers, err := mt.IsCustomPrivilegeGroup(groupName) + if err != nil { + return err + } + if definedByUsers { + return merr.WrapErrParameterInvalidMsg("privilege group name [%s] is defined by users", groupName) + } + if util.IsPrivilegeNameDefined(groupName) { + return merr.WrapErrParameterInvalidMsg("privilege group name [%s] is defined by built in privileges or privilege groups in system", groupName) + } + data := &milvuspb.PrivilegeGroupInfo{ + GroupName: groupName, + Privileges: make([]*milvuspb.PrivilegeEntity, 0), + } + return mt.catalog.SavePrivilegeGroup(mt.ctx, data) +} + +func (mt *MetaTable) DropPrivilegeGroup(groupName string) error { + if funcutil.IsEmptyString(groupName) { + return fmt.Errorf("the privilege group name is empty") + } + mt.permissionLock.Lock() + defer mt.permissionLock.Unlock() + + definedByUsers, err := mt.IsCustomPrivilegeGroup(groupName) + if err != nil { + return err + } + if !definedByUsers { + return nil + } + // check if the group is used by any role + roles, err := mt.catalog.ListRole(mt.ctx, util.DefaultTenant, nil, false) + if err != nil { + return err + } + roleEntity := lo.Map(roles, func(entity *milvuspb.RoleResult, _ int) *milvuspb.RoleEntity { + return entity.GetRole() + }) + for _, role := range roleEntity { + grants, err := mt.catalog.ListGrant(mt.ctx, util.DefaultTenant, &milvuspb.GrantEntity{ + Role: role, + DbName: util.AnyWord, + }) + if err != nil { + return err + } + for _, grant := range grants { + if grant.Grantor.Privilege.Name == groupName { + return errors.Newf("privilege group [%s] is used by role [%s], Use REVOKE API to revoke it first", groupName, role.GetName()) + } + } + } + return mt.catalog.DropPrivilegeGroup(mt.ctx, groupName) +} + +func (mt *MetaTable) ListPrivilegeGroups() ([]*milvuspb.PrivilegeGroupInfo, error) { + mt.permissionLock.Lock() + defer mt.permissionLock.Unlock() + + return mt.catalog.ListPrivilegeGroups(mt.ctx) +} + +func (mt *MetaTable) OperatePrivilegeGroup(groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error { + if funcutil.IsEmptyString(groupName) { + return fmt.Errorf("the privilege group name is empty") + } + mt.permissionLock.Lock() + defer mt.permissionLock.Unlock() + + // validate input params + definedByUsers, err := mt.IsCustomPrivilegeGroup(groupName) + if err != nil { + return err + } + if !definedByUsers { + return merr.WrapErrParameterInvalidMsg("there is no privilege group name [%s] to operate", groupName) + } + groups, err := mt.catalog.ListPrivilegeGroups(mt.ctx) + if err != nil { + return err + } + for _, p := range privileges { + if util.IsPrivilegeNameDefined(p.Name) { + continue + } + for _, group := range groups { + // add privileges for custom privilege group + if group.GroupName == p.Name { + privileges = append(privileges, group.Privileges...) + } else { + return merr.WrapErrParameterInvalidMsg("there is no privilege name or privielge group name [%s] defined in system to operate", p.Name) + } + } + } + + // merge with current privileges + group, err := mt.catalog.GetPrivilegeGroup(mt.ctx, groupName) + if err != nil { + log.Warn("fail to get privilege group", zap.String("privilege_group", groupName), zap.Error(err)) + return err + } + privSet := lo.SliceToMap(group.Privileges, func(p *milvuspb.PrivilegeEntity) (string, struct{}) { + return p.Name, struct{}{} + }) + switch operateType { + case milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup: + for _, p := range privileges { + privSet[p.Name] = struct{}{} + } + case milvuspb.OperatePrivilegeGroupType_RemovePrivilegesFromGroup: + for _, p := range privileges { + delete(privSet, p.Name) + } + default: + log.Warn("unsupported operate type", zap.Any("operate_type", operateType)) + return fmt.Errorf("unsupported operate type: %v", operateType) + } + + mergedPrivs := lo.Map(lo.Keys(privSet), func(priv string, _ int) *milvuspb.PrivilegeEntity { + return &milvuspb.PrivilegeEntity{Name: priv} + }) + data := &milvuspb.PrivilegeGroupInfo{ + GroupName: groupName, + Privileges: mergedPrivs, + } + return mt.catalog.SavePrivilegeGroup(mt.ctx, data) +} + +func (mt *MetaTable) GetPrivilegeGroupRoles(groupName string) ([]*milvuspb.RoleEntity, error) { + if funcutil.IsEmptyString(groupName) { + return nil, fmt.Errorf("the privilege group name is empty") + } + mt.permissionLock.RLock() + defer mt.permissionLock.RUnlock() + + // get all roles + roles, err := mt.catalog.ListRole(mt.ctx, util.DefaultTenant, nil, false) + if err != nil { + return nil, err + } + roleEntity := lo.Map(roles, func(entity *milvuspb.RoleResult, _ int) *milvuspb.RoleEntity { + return entity.GetRole() + }) + + rolesMap := make(map[*milvuspb.RoleEntity]struct{}) + for _, role := range roleEntity { + grants, err := mt.catalog.ListGrant(mt.ctx, util.DefaultTenant, &milvuspb.GrantEntity{ + Role: role, + DbName: util.AnyWord, + }) + if err != nil { + return nil, err + } + for _, grant := range grants { + if grant.Grantor.Privilege.Name == groupName { + rolesMap[role] = struct{}{} + } + } + } + return lo.Keys(rolesMap), nil +} diff --git a/internal/rootcoord/meta_table_test.go b/internal/rootcoord/meta_table_test.go index 7bd666f7ca3c4..4192b655c03be 100644 --- a/internal/rootcoord/meta_table_test.go +++ b/internal/rootcoord/meta_table_test.go @@ -2071,3 +2071,44 @@ func TestMetaTable_RestoreRBAC(t *testing.T) { err = mt.RestoreRBAC(context.TODO(), util.DefaultTenant, &milvuspb.RBACMeta{}) assert.Error(t, err) } + +func TestMetaTable_PrivilegeGroup(t *testing.T) { + catalog := mocks.NewRootCoordCatalog(t) + catalog.EXPECT().ListPrivilegeGroups(mock.Anything).Return([]*milvuspb.PrivilegeGroupInfo{ + { + GroupName: "pg1", + Privileges: []*milvuspb.PrivilegeEntity{{Name: "CreateCollection"}, {Name: "DescribeCollection"}}, + }, + }, nil) + catalog.EXPECT().ListRole(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) + catalog.EXPECT().SavePrivilegeGroup(mock.Anything, mock.Anything).Return(nil) + catalog.EXPECT().DropPrivilegeGroup(mock.Anything, mock.Anything).Return(nil) + mt := &MetaTable{ + dbName2Meta: map[string]*model.Database{ + "not_commit": model.NewDatabase(1, "not_commit", pb.DatabaseState_DatabaseCreated, nil), + }, + names: newNameDb(), + aliases: newNameDb(), + catalog: catalog, + } + err := mt.CreatePrivilegeGroup("pg1") + assert.Error(t, err) + err = mt.CreatePrivilegeGroup("") + assert.Error(t, err) + err = mt.CreatePrivilegeGroup("Insert") + assert.Error(t, err) + err = mt.CreatePrivilegeGroup("pg2") + assert.NoError(t, err) + err = mt.DropPrivilegeGroup("") + assert.Error(t, err) + err = mt.DropPrivilegeGroup("pg1") + assert.NoError(t, err) + err = mt.OperatePrivilegeGroup("", []*milvuspb.PrivilegeEntity{}, milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup) + assert.Error(t, err) + err = mt.OperatePrivilegeGroup("pg3", []*milvuspb.PrivilegeEntity{}, milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup) + assert.Error(t, err) + _, err = mt.GetPrivilegeGroupRoles("") + assert.Error(t, err) + _, err = mt.ListPrivilegeGroups() + assert.NoError(t, err) +} diff --git a/internal/rootcoord/mock_test.go b/internal/rootcoord/mock_test.go index e0eb4db1d8711..a9cf579fe5e85 100644 --- a/internal/rootcoord/mock_test.go +++ b/internal/rootcoord/mock_test.go @@ -96,6 +96,11 @@ type mockMetaTable struct { ListPolicyFunc func(tenant string) ([]string, error) ListUserRoleFunc func(tenant string) ([]string, error) DescribeDatabaseFunc func(ctx context.Context, dbName string) (*model.Database, error) + CreatePrivilegeGroupFunc func(groupName string) error + DropPrivilegeGroupFunc func(groupName string) error + ListPrivilegeGroupsFunc func() ([]*milvuspb.PrivilegeGroupInfo, error) + OperatePrivilegeGroupFunc func(groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error + GetPrivilegeGroupRolesFunc func(groupName string) ([]*milvuspb.RoleEntity, error) } func (m mockMetaTable) GetDatabaseByName(ctx context.Context, dbName string, ts Timestamp) (*model.Database, error) { @@ -250,6 +255,26 @@ func (m mockMetaTable) ListUserRole(tenant string) ([]string, error) { return m.ListUserRoleFunc(tenant) } +func (m mockMetaTable) CreatePrivilegeGroup(groupName string) error { + return m.CreatePrivilegeGroupFunc(groupName) +} + +func (m mockMetaTable) DropPrivilegeGroup(groupName string) error { + return m.DropPrivilegeGroupFunc(groupName) +} + +func (m mockMetaTable) ListPrivilegeGroups() ([]*milvuspb.PrivilegeGroupInfo, error) { + return m.ListPrivilegeGroupsFunc() +} + +func (m mockMetaTable) OperatePrivilegeGroup(groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error { + return m.OperatePrivilegeGroupFunc(groupName, privileges, operateType) +} + +func (m mockMetaTable) GetPrivilegeGroupRoles(groupName string) ([]*milvuspb.RoleEntity, error) { + return m.GetPrivilegeGroupRolesFunc(groupName) +} + func newMockMetaTable() *mockMetaTable { return &mockMetaTable{} } @@ -524,6 +549,21 @@ func withInvalidMeta() Opt { meta.DescribeDatabaseFunc = func(ctx context.Context, dbName string) (*model.Database, error) { return nil, errors.New("error mock DescribeDatabase") } + meta.CreatePrivilegeGroupFunc = func(groupName string) error { + return errors.New("error mock CreatePrivilegeGroup") + } + meta.DropPrivilegeGroupFunc = func(groupName string) error { + return errors.New("error mock DropPrivilegeGroup") + } + meta.ListPrivilegeGroupsFunc = func() ([]*milvuspb.PrivilegeGroupInfo, error) { + return nil, errors.New("error mock ListPrivilegeGroups") + } + meta.OperatePrivilegeGroupFunc = func(groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error { + return errors.New("error mock OperatePrivilegeGroup") + } + meta.GetPrivilegeGroupRolesFunc = func(groupName string) ([]*milvuspb.RoleEntity, error) { + return nil, errors.New("error mock GetPrivilegeGroupRoles") + } return withMeta(meta) } diff --git a/internal/rootcoord/mocks/meta_table.go b/internal/rootcoord/mocks/meta_table.go index 9dc0aee9c9e08..28f679f055991 100644 --- a/internal/rootcoord/mocks/meta_table.go +++ b/internal/rootcoord/mocks/meta_table.go @@ -570,6 +570,52 @@ func (_c *IMetaTable_CreateDatabase_Call) RunAndReturn(run func(context.Context, return _c } +// CreatePrivilegeGroup provides a mock function with given fields: groupName +func (_m *IMetaTable) CreatePrivilegeGroup(groupName string) error { + ret := _m.Called(groupName) + + if len(ret) == 0 { + panic("no return value specified for CreatePrivilegeGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(groupName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IMetaTable_CreatePrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatePrivilegeGroup' +type IMetaTable_CreatePrivilegeGroup_Call struct { + *mock.Call +} + +// CreatePrivilegeGroup is a helper method to define mock.On call +// - groupName string +func (_e *IMetaTable_Expecter) CreatePrivilegeGroup(groupName interface{}) *IMetaTable_CreatePrivilegeGroup_Call { + return &IMetaTable_CreatePrivilegeGroup_Call{Call: _e.mock.On("CreatePrivilegeGroup", groupName)} +} + +func (_c *IMetaTable_CreatePrivilegeGroup_Call) Run(run func(groupName string)) *IMetaTable_CreatePrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *IMetaTable_CreatePrivilegeGroup_Call) Return(_a0 error) *IMetaTable_CreatePrivilegeGroup_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *IMetaTable_CreatePrivilegeGroup_Call) RunAndReturn(run func(string) error) *IMetaTable_CreatePrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + // CreateRole provides a mock function with given fields: tenant, entity func (_m *IMetaTable) CreateRole(tenant string, entity *milvuspb.RoleEntity) error { ret := _m.Called(tenant, entity) @@ -842,6 +888,52 @@ func (_c *IMetaTable_DropGrant_Call) RunAndReturn(run func(string, *milvuspb.Rol return _c } +// DropPrivilegeGroup provides a mock function with given fields: groupName +func (_m *IMetaTable) DropPrivilegeGroup(groupName string) error { + ret := _m.Called(groupName) + + if len(ret) == 0 { + panic("no return value specified for DropPrivilegeGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(groupName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IMetaTable_DropPrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DropPrivilegeGroup' +type IMetaTable_DropPrivilegeGroup_Call struct { + *mock.Call +} + +// DropPrivilegeGroup is a helper method to define mock.On call +// - groupName string +func (_e *IMetaTable_Expecter) DropPrivilegeGroup(groupName interface{}) *IMetaTable_DropPrivilegeGroup_Call { + return &IMetaTable_DropPrivilegeGroup_Call{Call: _e.mock.On("DropPrivilegeGroup", groupName)} +} + +func (_c *IMetaTable_DropPrivilegeGroup_Call) Run(run func(groupName string)) *IMetaTable_DropPrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *IMetaTable_DropPrivilegeGroup_Call) Return(_a0 error) *IMetaTable_DropPrivilegeGroup_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *IMetaTable_DropPrivilegeGroup_Call) RunAndReturn(run func(string) error) *IMetaTable_DropPrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + // DropRole provides a mock function with given fields: tenant, roleName func (_m *IMetaTable) DropRole(tenant string, roleName string) error { ret := _m.Called(tenant, roleName) @@ -1265,6 +1357,64 @@ func (_c *IMetaTable_GetDatabaseByName_Call) RunAndReturn(run func(context.Conte return _c } +// GetPrivilegeGroupRoles provides a mock function with given fields: groupName +func (_m *IMetaTable) GetPrivilegeGroupRoles(groupName string) ([]*milvuspb.RoleEntity, error) { + ret := _m.Called(groupName) + + if len(ret) == 0 { + panic("no return value specified for GetPrivilegeGroupRoles") + } + + var r0 []*milvuspb.RoleEntity + var r1 error + if rf, ok := ret.Get(0).(func(string) ([]*milvuspb.RoleEntity, error)); ok { + return rf(groupName) + } + if rf, ok := ret.Get(0).(func(string) []*milvuspb.RoleEntity); ok { + r0 = rf(groupName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*milvuspb.RoleEntity) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(groupName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IMetaTable_GetPrivilegeGroupRoles_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPrivilegeGroupRoles' +type IMetaTable_GetPrivilegeGroupRoles_Call struct { + *mock.Call +} + +// GetPrivilegeGroupRoles is a helper method to define mock.On call +// - groupName string +func (_e *IMetaTable_Expecter) GetPrivilegeGroupRoles(groupName interface{}) *IMetaTable_GetPrivilegeGroupRoles_Call { + return &IMetaTable_GetPrivilegeGroupRoles_Call{Call: _e.mock.On("GetPrivilegeGroupRoles", groupName)} +} + +func (_c *IMetaTable_GetPrivilegeGroupRoles_Call) Run(run func(groupName string)) *IMetaTable_GetPrivilegeGroupRoles_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *IMetaTable_GetPrivilegeGroupRoles_Call) Return(_a0 []*milvuspb.RoleEntity, _a1 error) *IMetaTable_GetPrivilegeGroupRoles_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *IMetaTable_GetPrivilegeGroupRoles_Call) RunAndReturn(run func(string) ([]*milvuspb.RoleEntity, error)) *IMetaTable_GetPrivilegeGroupRoles_Call { + _c.Call.Return(run) + return _c +} + // IsAlias provides a mock function with given fields: db, name func (_m *IMetaTable) IsAlias(db string, name string) bool { ret := _m.Called(db, name) @@ -1308,6 +1458,62 @@ func (_c *IMetaTable_IsAlias_Call) RunAndReturn(run func(string, string) bool) * return _c } +// IsCustomPrivilegeGroup provides a mock function with given fields: groupName +func (_m *IMetaTable) IsCustomPrivilegeGroup(groupName string) (bool, error) { + ret := _m.Called(groupName) + + if len(ret) == 0 { + panic("no return value specified for IsCustomPrivilegeGroup") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(string) (bool, error)); ok { + return rf(groupName) + } + if rf, ok := ret.Get(0).(func(string) bool); ok { + r0 = rf(groupName) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(groupName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IMetaTable_IsCustomPrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsCustomPrivilegeGroup' +type IMetaTable_IsCustomPrivilegeGroup_Call struct { + *mock.Call +} + +// IsCustomPrivilegeGroup is a helper method to define mock.On call +// - groupName string +func (_e *IMetaTable_Expecter) IsCustomPrivilegeGroup(groupName interface{}) *IMetaTable_IsCustomPrivilegeGroup_Call { + return &IMetaTable_IsCustomPrivilegeGroup_Call{Call: _e.mock.On("IsCustomPrivilegeGroup", groupName)} +} + +func (_c *IMetaTable_IsCustomPrivilegeGroup_Call) Run(run func(groupName string)) *IMetaTable_IsCustomPrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *IMetaTable_IsCustomPrivilegeGroup_Call) Return(_a0 bool, _a1 error) *IMetaTable_IsCustomPrivilegeGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *IMetaTable_IsCustomPrivilegeGroup_Call) RunAndReturn(run func(string) (bool, error)) *IMetaTable_IsCustomPrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + // ListAliases provides a mock function with given fields: ctx, dbName, collectionName, ts func (_m *IMetaTable) ListAliases(ctx context.Context, dbName string, collectionName string, ts uint64) ([]string, error) { ret := _m.Called(ctx, dbName, collectionName, ts) @@ -1715,6 +1921,63 @@ func (_c *IMetaTable_ListPolicy_Call) RunAndReturn(run func(string) ([]string, e return _c } +// ListPrivilegeGroups provides a mock function with given fields: +func (_m *IMetaTable) ListPrivilegeGroups() ([]*milvuspb.PrivilegeGroupInfo, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for ListPrivilegeGroups") + } + + var r0 []*milvuspb.PrivilegeGroupInfo + var r1 error + if rf, ok := ret.Get(0).(func() ([]*milvuspb.PrivilegeGroupInfo, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []*milvuspb.PrivilegeGroupInfo); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*milvuspb.PrivilegeGroupInfo) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IMetaTable_ListPrivilegeGroups_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPrivilegeGroups' +type IMetaTable_ListPrivilegeGroups_Call struct { + *mock.Call +} + +// ListPrivilegeGroups is a helper method to define mock.On call +func (_e *IMetaTable_Expecter) ListPrivilegeGroups() *IMetaTable_ListPrivilegeGroups_Call { + return &IMetaTable_ListPrivilegeGroups_Call{Call: _e.mock.On("ListPrivilegeGroups")} +} + +func (_c *IMetaTable_ListPrivilegeGroups_Call) Run(run func()) *IMetaTable_ListPrivilegeGroups_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *IMetaTable_ListPrivilegeGroups_Call) Return(_a0 []*milvuspb.PrivilegeGroupInfo, _a1 error) *IMetaTable_ListPrivilegeGroups_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *IMetaTable_ListPrivilegeGroups_Call) RunAndReturn(run func() ([]*milvuspb.PrivilegeGroupInfo, error)) *IMetaTable_ListPrivilegeGroups_Call { + _c.Call.Return(run) + return _c +} + // ListUserRole provides a mock function with given fields: tenant func (_m *IMetaTable) ListUserRole(tenant string) ([]string, error) { ret := _m.Called(tenant) @@ -1813,6 +2076,54 @@ func (_c *IMetaTable_OperatePrivilege_Call) RunAndReturn(run func(string, *milvu return _c } +// OperatePrivilegeGroup provides a mock function with given fields: groupName, privileges, operateType +func (_m *IMetaTable) OperatePrivilegeGroup(groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error { + ret := _m.Called(groupName, privileges, operateType) + + if len(ret) == 0 { + panic("no return value specified for OperatePrivilegeGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, []*milvuspb.PrivilegeEntity, milvuspb.OperatePrivilegeGroupType) error); ok { + r0 = rf(groupName, privileges, operateType) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IMetaTable_OperatePrivilegeGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OperatePrivilegeGroup' +type IMetaTable_OperatePrivilegeGroup_Call struct { + *mock.Call +} + +// OperatePrivilegeGroup is a helper method to define mock.On call +// - groupName string +// - privileges []*milvuspb.PrivilegeEntity +// - operateType milvuspb.OperatePrivilegeGroupType +func (_e *IMetaTable_Expecter) OperatePrivilegeGroup(groupName interface{}, privileges interface{}, operateType interface{}) *IMetaTable_OperatePrivilegeGroup_Call { + return &IMetaTable_OperatePrivilegeGroup_Call{Call: _e.mock.On("OperatePrivilegeGroup", groupName, privileges, operateType)} +} + +func (_c *IMetaTable_OperatePrivilegeGroup_Call) Run(run func(groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType)) *IMetaTable_OperatePrivilegeGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].([]*milvuspb.PrivilegeEntity), args[2].(milvuspb.OperatePrivilegeGroupType)) + }) + return _c +} + +func (_c *IMetaTable_OperatePrivilegeGroup_Call) Return(_a0 error) *IMetaTable_OperatePrivilegeGroup_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *IMetaTable_OperatePrivilegeGroup_Call) RunAndReturn(run func(string, []*milvuspb.PrivilegeEntity, milvuspb.OperatePrivilegeGroupType) error) *IMetaTable_OperatePrivilegeGroup_Call { + _c.Call.Return(run) + return _c +} + // OperateUserRole provides a mock function with given fields: tenant, userEntity, roleEntity, operateType func (_m *IMetaTable) OperateUserRole(tenant string, userEntity *milvuspb.UserEntity, roleEntity *milvuspb.RoleEntity, operateType milvuspb.OperateUserRoleType) error { ret := _m.Called(tenant, userEntity, roleEntity, operateType) diff --git a/internal/rootcoord/root_coord.go b/internal/rootcoord/root_coord.go index 374cece06e9e0..27f66f13f56d6 100644 --- a/internal/rootcoord/root_coord.go +++ b/internal/rootcoord/root_coord.go @@ -612,7 +612,11 @@ func (c *Core) initBuiltinRoles() error { for _, privilege := range privilegesJSON[util.RoleConfigPrivileges] { privilegeName := privilege[util.RoleConfigPrivilege] if !util.IsAnyWord(privilege[util.RoleConfigPrivilege]) { - privilegeName = util.PrivilegeNameForMetastore(privilege[util.RoleConfigPrivilege]) + dbPrivName, err := c.getMetastorePrivilegeName(privilege[util.RoleConfigPrivilege]) + if err != nil { + return errors.Wrapf(err, "failed to get metastore privilege name for: %s", privilege[util.RoleConfigPrivilege]) + } + privilegeName = dbPrivName } err := c.meta.OperatePrivilege(util.DefaultTenant, &milvuspb.GrantEntity{ Role: &milvuspb.RoleEntity{Name: role}, @@ -2491,11 +2495,8 @@ func (c *Core) isValidGrantor(entity *milvuspb.GrantorEntity, object string) err if entity == nil { return errors.New("the grantor entity is nil") } - if entity.User == nil { - return errors.New("the user entity in the grantor entity is nil") - } - if entity.User.Name == "" { - return errors.New("the name in the user entity of the grantor entity is empty") + if entity.User == nil || entity.User.Name == "" { + return errors.New("the user entity in the grantor entity is nil or empty") } if _, err := c.meta.SelectUser(util.DefaultTenant, &milvuspb.UserEntity{Name: entity.User.Name}, false); err != nil { log.Warn("fail to select the user", zap.String("username", entity.User.Name), zap.Error(err)) @@ -2507,17 +2508,25 @@ func (c *Core) isValidGrantor(entity *milvuspb.GrantorEntity, object string) err if util.IsAnyWord(entity.Privilege.Name) { return nil } - if privilegeName := util.PrivilegeNameForMetastore(entity.Privilege.Name); privilegeName == "" { - return fmt.Errorf("not found the privilege name[%s]", entity.Privilege.Name) + // check object privileges for built-in privileges + if util.IsPrivilegeNameDefined(entity.Privilege.Name) { + privileges, ok := util.ObjectPrivileges[object] + if !ok { + return fmt.Errorf("not found the object type[name: %s], supported the object types: %v", object, lo.Keys(commonpb.ObjectType_value)) + } + for _, privilege := range privileges { + if privilege == entity.Privilege.Name { + return nil + } + } } - privileges, ok := util.ObjectPrivileges[object] - if !ok { - return fmt.Errorf("not found the object type[name: %s], supported the object types: %v", object, lo.Keys(commonpb.ObjectType_value)) + // check if it is a custom privilege group + customPrivGroup, err := c.meta.IsCustomPrivilegeGroup(entity.Privilege.Name) + if err != nil { + return err } - for _, privilege := range privileges { - if privilege == entity.Privilege.Name { - return nil - } + if customPrivGroup { + return nil } return fmt.Errorf("not found the privilege name[%s] in object[%s]", entity.Privilege.Name, object) } @@ -2562,11 +2571,18 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil } - ctxLog.Debug("before PrivilegeNameForMetastore", zap.String("privilege", in.Entity.Grantor.Privilege.Name)) - if !util.IsAnyWord(in.Entity.Grantor.Privilege.Name) { - in.Entity.Grantor.Privilege.Name = util.PrivilegeNameForMetastore(in.Entity.Grantor.Privilege.Name) + // set up privilege name for metastore + privName := in.Entity.Grantor.Privilege.Name + ctxLog.Debug("before PrivilegeNameForMetastore", zap.String("privilege", privName)) + if !util.IsAnyWord(privName) { + dbPrivName, err := c.getMetastorePrivilegeName(privName) + if err != nil { + return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil + } + in.Entity.Grantor.Privilege.Name = dbPrivName } - ctxLog.Debug("after PrivilegeNameForMetastore", zap.String("privilege", in.Entity.Grantor.Privilege.Name)) + ctxLog.Debug("after PrivilegeNameForMetastore", zap.String("privilege", privName)) + if in.Entity.Object.Name == commonpb.ObjectType_Global.String() { in.Entity.ObjectName = util.AnyWord } @@ -2614,6 +2630,22 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile return merr.Success(), nil } +func (c *Core) getMetastorePrivilegeName(privName string) (string, error) { + // if it is built-in privilege, return the privilege name directly + if util.IsPrivilegeNameDefined(privName) { + return util.PrivilegeNameForMetastore(privName), nil + } + // return the privilege group name if it is a custom privilege group + customGroup, err := c.meta.IsCustomPrivilegeGroup(privName) + if err != nil { + return "", err + } + if customGroup { + return util.PrivilegeGroupNameForMetastore(privName), nil + } + return "", errors.New("not found the privilege name") +} + // SelectGrant select grant // - check the node health // - check if the principal entity is valid @@ -2706,14 +2738,23 @@ func (c *Core) ListPolicy(ctx context.Context, in *internalpb.ListPolicyRequest) Status: merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_ListPolicyFailure), }, nil } + privGroups, err := c.meta.ListPrivilegeGroups() + if err != nil { + errMsg := "fail to list privilege groups" + ctxLog.Warn(errMsg, zap.Error(err)) + return &internalpb.ListPolicyResponse{ + Status: merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_ListPolicyFailure), + }, nil + } ctxLog.Debug(method + " success") metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.SuccessLabel).Inc() metrics.RootCoordDDLReqLatency.WithLabelValues(method).Observe(float64(tr.ElapseSpan().Milliseconds())) return &internalpb.ListPolicyResponse{ - Status: merr.Success(), - PolicyInfos: policies, - UserRoles: userRoles, + Status: merr.Success(), + PolicyInfos: policies, + UserRoles: userRoles, + PrivilegeGroups: privGroups, }, nil } @@ -2914,3 +2955,236 @@ func (c *Core) CheckHealth(ctx context.Context, in *milvuspb.CheckHealthRequest) return &milvuspb.CheckHealthResponse{Status: merr.Success(), IsHealthy: true, Reasons: []string{}}, nil } + +func (c *Core) CreatePrivilegeGroup(ctx context.Context, in *milvuspb.CreatePrivilegeGroupRequest) (*commonpb.Status, error) { + method := "CreatePrivilegeGroup" + metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.TotalLabel).Inc() + tr := timerecord.NewTimeRecorder(method) + ctxLog := log.Ctx(ctx).With(zap.String("role", typeutil.RootCoordRole), zap.Any("in", in)) + ctxLog.Debug(method) + + if err := merr.CheckHealthy(c.GetStateCode()); err != nil { + return merr.Status(err), nil + } + + if err := c.meta.CreatePrivilegeGroup(in.GroupName); err != nil { + ctxLog.Warn("fail to create privilege group", zap.Error(err)) + return merr.Status(err), nil + } + + ctxLog.Debug(method + " success") + metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.SuccessLabel).Inc() + metrics.RootCoordDDLReqLatency.WithLabelValues(method).Observe(float64(tr.ElapseSpan().Milliseconds())) + metrics.RootCoordNumOfPrivilegeGroups.Inc() + return merr.Success(), nil +} + +func (c *Core) DropPrivilegeGroup(ctx context.Context, in *milvuspb.DropPrivilegeGroupRequest) (*commonpb.Status, error) { + method := "DropPrivilegeGroup" + metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.TotalLabel).Inc() + tr := timerecord.NewTimeRecorder(method) + ctxLog := log.Ctx(ctx).With(zap.String("role", typeutil.RootCoordRole), zap.Any("in", in)) + ctxLog.Debug(method) + + if err := merr.CheckHealthy(c.GetStateCode()); err != nil { + return merr.Status(err), nil + } + + if err := c.meta.DropPrivilegeGroup(in.GroupName); err != nil { + ctxLog.Warn("fail to drop privilege group", zap.Error(err)) + return merr.Status(err), nil + } + + ctxLog.Debug(method + " success") + metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.SuccessLabel).Inc() + metrics.RootCoordDDLReqLatency.WithLabelValues(method).Observe(float64(tr.ElapseSpan().Milliseconds())) + metrics.RootCoordNumOfPrivilegeGroups.Desc() + return merr.Success(), nil +} + +func (c *Core) ListPrivilegeGroups(ctx context.Context, in *milvuspb.ListPrivilegeGroupsRequest) (*milvuspb.ListPrivilegeGroupsResponse, error) { + method := "ListPrivilegeGroups" + metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.TotalLabel).Inc() + tr := timerecord.NewTimeRecorder(method) + ctxLog := log.Ctx(ctx).With(zap.String("role", typeutil.RootCoordRole), zap.Any("in", in)) + ctxLog.Debug(method) + + if err := merr.CheckHealthy(c.GetStateCode()); err != nil { + return &milvuspb.ListPrivilegeGroupsResponse{ + Status: merr.Status(err), + }, nil + } + + privGroups, err := c.meta.ListPrivilegeGroups() + if err != nil { + ctxLog.Warn("fail to list privilege group", zap.Error(err)) + return &milvuspb.ListPrivilegeGroupsResponse{ + Status: merr.StatusWithErrorCode(err, commonpb.ErrorCode_ListPrivilegeGroupsFailure), + }, nil + } + + ctxLog.Debug(method + " success") + metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.SuccessLabel).Inc() + metrics.RootCoordDDLReqLatency.WithLabelValues(method).Observe(float64(tr.ElapseSpan().Milliseconds())) + return &milvuspb.ListPrivilegeGroupsResponse{ + Status: merr.Success(), + PrivilegeGroups: privGroups, + }, nil +} + +func (c *Core) OperatePrivilegeGroup(ctx context.Context, in *milvuspb.OperatePrivilegeGroupRequest) (*commonpb.Status, error) { + method := "OperatePrivilegeGroup-" + in.Type.String() + metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.TotalLabel).Inc() + tr := timerecord.NewTimeRecorder(method) + ctxLog := log.Ctx(ctx).With(zap.String("role", typeutil.RootCoordRole), zap.Any("in", in)) + ctxLog.Debug(method) + + if err := merr.CheckHealthy(c.GetStateCode()); err != nil { + return merr.Status(err), nil + } + + redoTask := newBaseRedoTask(c.stepExecutor) + redoTask.AddSyncStep(NewSimpleStep("operate privilege group", func(ctx context.Context) ([]nestedStep, error) { + groups, err := c.meta.ListPrivilegeGroups() + if err != nil && !common.IsIgnorableError(err) { + log.Warn("fail to list privilege groups", zap.Error(err)) + return nil, err + } + currGroups := lo.SliceToMap(groups, func(group *milvuspb.PrivilegeGroupInfo) (string, []*milvuspb.PrivilegeEntity) { + return group.GroupName, group.Privileges + }) + + // get roles granted to the group + roles, err := c.meta.GetPrivilegeGroupRoles(in.GroupName) + if err != nil { + return nil, err + } + + newGroups := make(map[string][]*milvuspb.PrivilegeEntity) + for k, v := range currGroups { + if k != in.GroupName { + newGroups[k] = v + continue + } + switch in.Type { + case milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup: + newPrivs := lo.Union(v, in.Privileges) + newGroups[k] = lo.UniqBy(newPrivs, func(p *milvuspb.PrivilegeEntity) string { + return p.Name + }) + case milvuspb.OperatePrivilegeGroupType_RemovePrivilegesFromGroup: + newPrivs, _ := lo.Difference(v, in.Privileges) + newGroups[k] = newPrivs + default: + return nil, errors.New("invalid operate type") + } + } + + rolesToRevoke := []*milvuspb.GrantEntity{} + rolesToGrant := []*milvuspb.GrantEntity{} + compareGrants := func(a, b *milvuspb.GrantEntity) bool { + return a.Role.GetName() == b.Role.GetName() && + a.Object.GetName() == b.Object.GetName() && + a.ObjectName == b.ObjectName && + a.Grantor.GetUser().GetName() == b.Grantor.GetUser().GetName() && + a.Grantor.GetPrivilege().GetName() == b.Grantor.GetPrivilege().GetName() && + a.DbName == b.DbName + } + for _, role := range roles { + grants, err := c.meta.SelectGrant(util.DefaultTenant, &milvuspb.GrantEntity{ + Role: role, + DbName: util.AnyWord, + }) + if err != nil { + return nil, err + } + currGrants := c.expandPrivilegeGroups(grants, currGroups) + newGrants := c.expandPrivilegeGroups(grants, newGroups) + + toRevoke := lo.Filter(currGrants, func(item *milvuspb.GrantEntity, _ int) bool { + return !lo.ContainsBy(newGrants, func(newItem *milvuspb.GrantEntity) bool { + return compareGrants(item, newItem) + }) + }) + + toGrant := lo.Filter(newGrants, func(item *milvuspb.GrantEntity, _ int) bool { + return !lo.ContainsBy(currGrants, func(currItem *milvuspb.GrantEntity) bool { + return compareGrants(item, currItem) + }) + }) + + rolesToRevoke = append(rolesToRevoke, toRevoke...) + rolesToGrant = append(rolesToGrant, toGrant...) + } + + if len(rolesToRevoke) > 0 { + opType := int32(typeutil.CacheRevokePrivilege) + if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ + OpType: opType, + OpKey: funcutil.PolicyForPrivileges(rolesToRevoke), + }); err != nil { + log.Warn("fail to refresh policy info cache for revoke privileges in operate privilege group", zap.Any("in", in), zap.Error(err)) + return nil, err + } + } + + if len(rolesToGrant) > 0 { + opType := int32(typeutil.CacheGrantPrivilege) + if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ + OpType: opType, + OpKey: funcutil.PolicyForPrivileges(rolesToGrant), + }); err != nil { + log.Warn("fail to refresh policy info cache for grants privilege in operate privilege group", zap.Any("in", in), zap.Error(err)) + return nil, err + } + } + return nil, nil + })) + + redoTask.AddSyncStep(NewSimpleStep("operate privilege group meta data", func(ctx context.Context) ([]nestedStep, error) { + err := c.meta.OperatePrivilegeGroup(in.GroupName, in.Privileges, in.Type) + if err != nil && !common.IsIgnorableError(err) { + log.Warn("fail to operate privilege group", zap.Error(err)) + } + return nil, err + })) + + err := redoTask.Execute(ctx) + if err != nil { + errMsg := "fail to execute task when operate privilege group" + ctxLog.Warn(errMsg, zap.Error(err)) + status := merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_OperatePrivilegeGroupFailure) + return status, nil + } + + ctxLog.Debug(method + " success") + metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.SuccessLabel).Inc() + metrics.RootCoordDDLReqLatency.WithLabelValues(method).Observe(float64(tr.ElapseSpan().Milliseconds())) + return merr.Success(), nil +} + +func (c *Core) expandPrivilegeGroups(grants []*milvuspb.GrantEntity, groups map[string][]*milvuspb.PrivilegeEntity) []*milvuspb.GrantEntity { + newGrants := []*milvuspb.GrantEntity{} + for _, grant := range grants { + if groups[grant.Grantor.Privilege.Name] == nil { + newGrants = append(newGrants, grant) + } else { + for _, priv := range groups[grant.Grantor.Privilege.Name] { + newGrants = append(newGrants, &milvuspb.GrantEntity{ + Role: grant.Role, + Object: grant.Object, + ObjectName: grant.ObjectName, + Grantor: &milvuspb.GrantorEntity{ + User: grant.Grantor.User, + Privilege: priv, + }, + DbName: grant.DbName, + }) + } + } + } + // uniq by role + object + object name + grantor user + privilege name + db name + return lo.UniqBy(newGrants, func(g *milvuspb.GrantEntity) string { + return fmt.Sprintf("%s-%s-%s-%s-%s-%s", g.Role, g.Object, g.ObjectName, g.Grantor.User, g.Grantor.Privilege.Name, g.DbName) + }) +} diff --git a/internal/rootcoord/root_coord_test.go b/internal/rootcoord/root_coord_test.go index 689bfee948a5a..bdf93e15a9a41 100644 --- a/internal/rootcoord/root_coord_test.go +++ b/internal/rootcoord/root_coord_test.go @@ -1748,6 +1748,9 @@ func TestRootCoord_RBACError(t *testing.T) { mockMeta.SelectRoleFunc = func(tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) { return nil, nil } + mockMeta.ListPrivilegeGroupsFunc = func() ([]*milvuspb.PrivilegeGroupInfo, error) { + return nil, nil + } { resp, err := c.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{Entity: &milvuspb.GrantEntity{ Role: &milvuspb.RoleEntity{Name: "foo"}, @@ -1784,6 +1787,39 @@ func TestRootCoord_RBACError(t *testing.T) { } }) + t.Run("operate privilege group failed", func(t *testing.T) { + mockMeta := c.meta.(*mockMetaTable) + mockMeta.ListPrivilegeGroupsFunc = func() ([]*milvuspb.PrivilegeGroupInfo, error) { + return nil, errors.New("mock error") + } + mockMeta.CreatePrivilegeGroupFunc = func(groupName string) error { + return errors.New("mock error") + } + mockMeta.GetPrivilegeGroupRolesFunc = func(groupName string) ([]*milvuspb.RoleEntity, error) { + return nil, errors.New("mock error") + } + { + resp, err := c.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{}) + assert.NoError(t, err) + assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetErrorCode()) + } + { + resp, err := c.ListPrivilegeGroups(ctx, &milvuspb.ListPrivilegeGroupsRequest{}) + assert.NoError(t, err) + assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) + } + { + resp, err := c.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{}) + assert.NoError(t, err) + assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetErrorCode()) + } + { + resp, err := c.CreatePrivilegeGroup(ctx, &milvuspb.CreatePrivilegeGroupRequest{}) + assert.NoError(t, err) + assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetErrorCode()) + } + }) + t.Run("select grant failed", func(t *testing.T) { { resp, err := c.SelectGrant(ctx, &milvuspb.SelectGrantRequest{}) @@ -1879,6 +1915,9 @@ func TestRootCoord_BuiltinRoles(t *testing.T) { mockMeta.OperatePrivilegeFunc = func(tenant string, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error { return nil } + mockMeta.ListPrivilegeGroupsFunc = func() ([]*milvuspb.PrivilegeGroupInfo, error) { + return nil, nil + } err := c.initBuiltinRoles() assert.Equal(t, nil, err) assert.True(t, util.IsBuiltinRole(roleDbAdmin)) diff --git a/internal/util/mock/grpc_rootcoord_client.go b/internal/util/mock/grpc_rootcoord_client.go index bc80d954ae250..0c5d431af4bd8 100644 --- a/internal/util/mock/grpc_rootcoord_client.go +++ b/internal/util/mock/grpc_rootcoord_client.go @@ -270,6 +270,22 @@ func (m *GrpcRootCoordClient) RestoreRBAC(ctx context.Context, in *milvuspb.Rest return &commonpb.Status{}, m.Err } +func (m *GrpcRootCoordClient) CreatePrivilegeGroup(ctx context.Context, in *milvuspb.CreatePrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + return &commonpb.Status{}, m.Err +} + +func (m *GrpcRootCoordClient) DropPrivilegeGroup(ctx context.Context, in *milvuspb.DropPrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + return &commonpb.Status{}, m.Err +} + +func (m *GrpcRootCoordClient) ListPrivilegeGroups(ctx context.Context, in *milvuspb.ListPrivilegeGroupsRequest, opts ...grpc.CallOption) (*milvuspb.ListPrivilegeGroupsResponse, error) { + return &milvuspb.ListPrivilegeGroupsResponse{}, m.Err +} + +func (m *GrpcRootCoordClient) OperatePrivilegeGroup(ctx context.Context, in *milvuspb.OperatePrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + return &commonpb.Status{}, m.Err +} + func (m *GrpcRootCoordClient) Close() error { return nil } diff --git a/pkg/metrics/rootcoord_metrics.go b/pkg/metrics/rootcoord_metrics.go index 582207c61b0e9..90ed0b6b7abe5 100644 --- a/pkg/metrics/rootcoord_metrics.go +++ b/pkg/metrics/rootcoord_metrics.go @@ -146,6 +146,15 @@ var ( Help: "The number of roles", }) + // RootCoordNumOfPrivilegeGroups counts the number of credentials. + RootCoordNumOfPrivilegeGroups = prometheus.NewGauge( + prometheus.GaugeOpts{ + Namespace: milvusNamespace, + Subsystem: typeutil.RootCoordRole, + Name: "num_of_privilege_groups", + Help: "The number of privilege groups", + }) + // RootCoordTtDelay records the max time tick delay of flow graphs in DataNodes and QueryNodes. RootCoordTtDelay = prometheus.NewGaugeVec( prometheus.GaugeOpts{ diff --git a/pkg/util/constant.go b/pkg/util/constant.go index e51206875a2d4..d68ac7fce276a 100644 --- a/pkg/util/constant.go +++ b/pkg/util/constant.go @@ -21,6 +21,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v2/commonpb" "github.com/milvus-io/milvus/pkg/common" + "github.com/milvus-io/milvus/pkg/util/typeutil" ) // Meta Prefix consts @@ -114,6 +115,10 @@ var ( MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGetFlushState.String()), MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupReadOnly.String()), MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupReadWrite.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreatePrivilegeGroup.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropPrivilegeGroup.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListPrivilegeGroups.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeOperatePrivilegeGroup.String()), }, commonpb.ObjectType_Global.String(): { MetaStore2API(commonpb.ObjectPrivilege_PrivilegeAll.String()), @@ -151,6 +156,10 @@ var ( MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDescribeAlias.String()), MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListAliases.String()), MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupAdmin.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreatePrivilegeGroup.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropPrivilegeGroup.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListPrivilegeGroups.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeOperatePrivilegeGroup.String()), }, commonpb.ObjectType_User.String(): { MetaStore2API(commonpb.ObjectPrivilege_PrivilegeUpdateUser.String()), @@ -306,6 +315,9 @@ func MetaStore2API(name string) string { func PrivilegeNameForAPI(name string) string { _, ok := commonpb.ObjectPrivilege_value[name] if !ok { + if strings.HasPrefix(name, PrivilegeGroupWord) { + return typeutil.After(name, PrivilegeGroupWord) + } return "" } return MetaStore2API(name) @@ -327,6 +339,15 @@ func PrivilegeNameForMetastore(name string) string { return dbPrivilege } +// check if the name is defined by built in privileges or privilege groups in system +func IsPrivilegeNameDefined(name string) bool { + return PrivilegeNameForMetastore(name) != "" +} + +func PrivilegeGroupNameForMetastore(name string) string { + return PrivilegeGroupWord + name +} + func IsAnyWord(word string) bool { return word == AnyWord } diff --git a/pkg/util/funcutil/policy.go b/pkg/util/funcutil/policy.go index 384d3a436f1b8..bdf2650a51dab 100644 --- a/pkg/util/funcutil/policy.go +++ b/pkg/util/funcutil/policy.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/cockroachdb/errors" + "github.com/samber/lo" "go.uber.org/zap" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" @@ -114,6 +115,16 @@ func PolicyForPrivilege(roleName string, objectType string, objectName string, p return fmt.Sprintf(`{"PType":"p","V0":"%s","V1":"%s","V2":"%s"}`, roleName, PolicyForResource(dbName, objectType, objectName), privilege) } +func PolicyForPrivileges(grants []*milvuspb.GrantEntity) string { + return strings.Join(lo.Map(grants, func(r *milvuspb.GrantEntity, _ int) string { + return PolicyForPrivilege(r.Role.Name, r.Object.Name, r.ObjectName, r.Grantor.Privilege.Name, r.DbName) + }), "|") +} + +func PrivilegesForPolicy(policy string) []string { + return strings.Split(policy, "|") +} + func PolicyForResource(dbName string, objectType string, objectName string) string { return fmt.Sprintf("%s-%s", objectType, CombineObjectName(dbName, objectName)) } diff --git a/tests/integration/rbac/privilege_group_test.go b/tests/integration/rbac/privilege_group_test.go index da89b603a472e..bea3af0514737 100644 --- a/tests/integration/rbac/privilege_group_test.go +++ b/tests/integration/rbac/privilege_group_test.go @@ -17,6 +17,7 @@ package rbac import ( "context" + "fmt" "testing" "github.com/stretchr/testify/suite" @@ -41,94 +42,229 @@ func (s *PrivilegeGroupTestSuite) SetupSuite() { paramtable.Get().Save(paramtable.Get().CommonCfg.AuthorizationEnabled.Key, "true") } -func (s *PrivilegeGroupTestSuite) TestPrivilegeGroup() { +func (s *PrivilegeGroupTestSuite) TestBuiltinPrivilegeGroup() { ctx := GetContext(context.Background(), "root:123456") - // test empty rbac content + + // Test empty RBAC content resp, err := s.Cluster.Proxy.BackupRBAC(ctx, &milvuspb.BackupRBACMetaRequest{}) s.NoError(err) s.True(merr.Ok(resp.GetStatus())) s.Equal("", resp.GetRBACMeta().String()) - // generate some rbac content + // Generate some RBAC content roleName := "test_role" - resp1, err := s.Cluster.Proxy.CreateRole(ctx, &milvuspb.CreateRoleRequest{ - Entity: &milvuspb.RoleEntity{ - Name: roleName, - }, + createRoleResp, err := s.Cluster.Proxy.CreateRole(ctx, &milvuspb.CreateRoleRequest{ + Entity: &milvuspb.RoleEntity{Name: roleName}, }) s.NoError(err) - s.True(merr.Ok(resp1)) - resp2, err := s.Cluster.Proxy.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{ - Type: milvuspb.OperatePrivilegeType_Grant, - Entity: &milvuspb.GrantEntity{ - Role: &milvuspb.RoleEntity{Name: roleName}, - Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Collection.String()}, - ObjectName: util.AnyWord, - DbName: util.AnyWord, - Grantor: &milvuspb.GrantorEntity{ - User: &milvuspb.UserEntity{Name: util.UserRoot}, - Privilege: &milvuspb.PrivilegeEntity{Name: "ReadOnly"}, - }, - }, + s.True(merr.Ok(createRoleResp)) + + s.operatePrivilege(ctx, roleName, "ReadOnly", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Grant) + s.operatePrivilege(ctx, roleName, "ReadWrite", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Grant) + s.operatePrivilege(ctx, roleName, "Admin", commonpb.ObjectType_Global.String(), milvuspb.OperatePrivilegeType_Grant) + + s.validateGrants(ctx, roleName, commonpb.ObjectType_Global.String(), 1) + s.validateGrants(ctx, roleName, commonpb.ObjectType_Collection.String(), 2) +} + +/* +create group1: query, search +grant insert to role -> role: insert +grant group1 to role -> role: insert, group1(query, search) +create group2: query, delete +grant group2 to role -> role: insert, group1(query, search), group2(query, delete) +add query, load to group1 -> group1: query, search, load -> role: insert, group1(query, search, load), group2(query, delete) +remove query from group1 -> group1: search, load -> role: insert, group1(search, load), group2(query, delete), role still have query privilege because of group2 granted. +*/ +func (s *PrivilegeGroupTestSuite) TestCustomPrivilegeGroup() { + ctx := GetContext(context.Background(), "root:123456") + + // Helper function to operate on privilege groups + operatePrivilegeGroup := func(groupName string, operateType milvuspb.OperatePrivilegeGroupType, privileges []*milvuspb.PrivilegeEntity) { + resp, err := s.Cluster.Proxy.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{ + GroupName: groupName, + Type: operateType, + Privileges: privileges, + }) + s.NoError(err) + s.True(merr.Ok(resp)) + } + + // Helper function to list privilege groups and return the target group and its privileges + validatePrivilegeGroup := func(groupName string, privileges int) []*milvuspb.PrivilegeEntity { + resp, err := s.Cluster.Proxy.ListPrivilegeGroups(ctx, &milvuspb.ListPrivilegeGroupsRequest{}) + s.NoError(err) + for _, privGroup := range resp.PrivilegeGroups { + if privGroup.GroupName == groupName { + s.Equal(privileges, len(privGroup.Privileges)) + return privGroup.Privileges + } + } + return nil + } + + // create group1: query, search + createResp, err := s.Cluster.Proxy.CreatePrivilegeGroup(ctx, &milvuspb.CreatePrivilegeGroupRequest{ + GroupName: "group1", }) s.NoError(err) - s.True(merr.Ok(resp2)) + s.True(merr.Ok(createResp)) + validatePrivilegeGroup("group1", 0) + operatePrivilegeGroup("group1", milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup, []*milvuspb.PrivilegeEntity{ + {Name: "Query"}, + {Name: "Search"}, + }) + validatePrivilegeGroup("group1", 2) - resp3, err := s.Cluster.Proxy.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{ - Type: milvuspb.OperatePrivilegeType_Grant, - Entity: &milvuspb.GrantEntity{ - Role: &milvuspb.RoleEntity{Name: roleName}, - Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Collection.String()}, - ObjectName: util.AnyWord, - DbName: util.AnyWord, - Grantor: &milvuspb.GrantorEntity{ - User: &milvuspb.UserEntity{Name: util.UserRoot}, - Privilege: &milvuspb.PrivilegeEntity{Name: "ReadWrite"}, - }, - }, + // grant insert to role -> role: insert + role := "role1" + createRoleResp, err := s.Cluster.Proxy.CreateRole(ctx, &milvuspb.CreateRoleRequest{ + Entity: &milvuspb.RoleEntity{Name: role}, }) s.NoError(err) - s.True(merr.Ok(resp3)) + s.True(merr.Ok(createRoleResp)) + s.operatePrivilege(ctx, role, "Insert", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Grant) + s.validateGrants(ctx, role, commonpb.ObjectType_Collection.String(), 1) - resp4, err := s.Cluster.Proxy.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{ - Type: milvuspb.OperatePrivilegeType_Grant, - Entity: &milvuspb.GrantEntity{ - Role: &milvuspb.RoleEntity{Name: roleName}, - Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Global.String()}, - ObjectName: util.AnyWord, - DbName: util.AnyWord, - Grantor: &milvuspb.GrantorEntity{ - User: &milvuspb.UserEntity{Name: util.UserRoot}, - Privilege: &milvuspb.PrivilegeEntity{Name: "Admin"}, - }, - }, + // grant group1 to role -> role: insert, group1(query, search) + s.operatePrivilege(ctx, role, "group1", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Grant) + s.validateGrants(ctx, role, commonpb.ObjectType_Collection.String(), 2) + + // create group2: query, delete + createResp2, err := s.Cluster.Proxy.CreatePrivilegeGroup(ctx, &milvuspb.CreatePrivilegeGroupRequest{ + GroupName: "group2", }) s.NoError(err) - s.True(merr.Ok(resp4)) + s.True(merr.Ok(createResp2)) + validatePrivilegeGroup("group2", 0) + operatePrivilegeGroup("group2", milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup, []*milvuspb.PrivilegeEntity{ + {Name: "Query"}, + {Name: "Delete"}, + }) + validatePrivilegeGroup("group2", 2) + + // grant group2 to role -> role: insert, group1(query, search), group2(query, delete) + s.operatePrivilege(ctx, role, "group2", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Grant) + s.validateGrants(ctx, role, commonpb.ObjectType_Collection.String(), 3) - resp5, err := s.Cluster.Proxy.SelectGrant(ctx, &milvuspb.SelectGrantRequest{ + // add query, load to group1 -> group1: query, search, load -> role: insert, group1(query, search, load), group2(query, delete) + operatePrivilegeGroup("group1", milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup, []*milvuspb.PrivilegeEntity{ + {Name: "Query"}, + {Name: "Load"}, + }) + validatePrivilegeGroup("group1", 3) + s.validateGrants(ctx, role, commonpb.ObjectType_Collection.String(), 3) + + // remove query from group1 -> group1: search, load -> role: insert, group1(search, load), group2(query, delete), role still have query privilege because of group2 granted. + operatePrivilegeGroup("group1", milvuspb.OperatePrivilegeGroupType_RemovePrivilegesFromGroup, []*milvuspb.PrivilegeEntity{ + {Name: "Query"}, + }) + validatePrivilegeGroup("group1", 2) + s.validateGrants(ctx, role, commonpb.ObjectType_Collection.String(), 3) + + // Drop the group during any role usage will cause error + dropResp, _ := s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{ + GroupName: "group1", + }) + s.Error(merr.Error(dropResp)) + + // Revoke privilege group and privileges + s.operatePrivilege(ctx, role, "group1", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Revoke) + s.operatePrivilege(ctx, role, "group2", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Revoke) + s.operatePrivilege(ctx, role, "Insert", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Revoke) + + // Drop the privilege group after revoking the privilege will succeed + dropResp, err = s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{ + GroupName: "group1", + }) + s.NoError(err) + s.True(merr.Ok(dropResp)) + + dropResp, err = s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{ + GroupName: "group2", + }) + s.NoError(err) + s.True(merr.Ok(dropResp)) + + // Validate the group was dropped + resp, err := s.Cluster.Proxy.ListPrivilegeGroups(ctx, &milvuspb.ListPrivilegeGroupsRequest{}) + s.NoError(err) + s.Equal(0, len(resp.PrivilegeGroups)) + + // Drop the role + dropRoleResp, err := s.Cluster.Proxy.DropRole(ctx, &milvuspb.DropRoleRequest{ + RoleName: role, + }) + s.NoError(err) + s.True(merr.Ok(dropRoleResp)) +} + +func (s *PrivilegeGroupTestSuite) TestInvalidPrivilegeGroup() { + ctx := GetContext(context.Background(), "root:123456") + + createResp, err := s.Cluster.Proxy.CreatePrivilegeGroup(ctx, &milvuspb.CreatePrivilegeGroupRequest{ + GroupName: "", + }) + s.NoError(err) + s.False(merr.Ok(createResp)) + + dropResp, err := s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{ + GroupName: "group1", + }) + s.NoError(err) + s.True(merr.Ok(dropResp)) + + dropResp, err = s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{ + GroupName: "", + }) + s.NoError(err) + s.False(merr.Ok(dropResp)) + + operateResp, err := s.Cluster.Proxy.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{ + GroupName: "", + }) + s.NoError(err) + s.False(merr.Ok(operateResp)) + + operateResp, err = s.Cluster.Proxy.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{ + GroupName: "group1", + Privileges: []*milvuspb.PrivilegeEntity{{Name: "123"}}, + }) + s.NoError(err) + s.False(merr.Ok(operateResp)) +} + +func (s *PrivilegeGroupTestSuite) operatePrivilege(ctx context.Context, role, privilege, objectType string, operateType milvuspb.OperatePrivilegeType) { + resp, err := s.Cluster.Proxy.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{ + Type: operateType, Entity: &milvuspb.GrantEntity{ - Role: &milvuspb.RoleEntity{Name: roleName}, - Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Global.String()}, + Role: &milvuspb.RoleEntity{Name: role}, + Object: &milvuspb.ObjectEntity{Name: objectType}, ObjectName: util.AnyWord, DbName: util.AnyWord, + Grantor: &milvuspb.GrantorEntity{ + User: &milvuspb.UserEntity{Name: util.UserRoot}, + Privilege: &milvuspb.PrivilegeEntity{Name: privilege}, + }, }, }) s.NoError(err) - s.True(merr.Ok(resp5.GetStatus())) - s.Len(resp5.GetEntities(), 1) + s.True(merr.Ok(resp)) +} - resp6, err := s.Cluster.Proxy.SelectGrant(ctx, &milvuspb.SelectGrantRequest{ +func (s *PrivilegeGroupTestSuite) validateGrants(ctx context.Context, roleName, objectType string, expectedCount int) { + resp, err := s.Cluster.Proxy.SelectGrant(ctx, &milvuspb.SelectGrantRequest{ Entity: &milvuspb.GrantEntity{ Role: &milvuspb.RoleEntity{Name: roleName}, - Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Collection.String()}, + Object: &milvuspb.ObjectEntity{Name: objectType}, ObjectName: util.AnyWord, DbName: util.AnyWord, }, }) + fmt.Println("!!!validateGrants: ", resp) s.NoError(err) - s.True(merr.Ok(resp6.GetStatus())) - s.Len(resp6.GetEntities(), 2) + s.True(merr.Ok(resp.GetStatus())) + s.Len(resp.GetEntities(), expectedCount) } func TestPrivilegeGroup(t *testing.T) { diff --git a/tests/integration/rbac/rbac_backup_test.go b/tests/integration/rbac/rbac_backup_test.go index de4e271e9163a..5bb1e2adb7260 100644 --- a/tests/integration/rbac/rbac_backup_test.go +++ b/tests/integration/rbac/rbac_backup_test.go @@ -20,6 +20,7 @@ import ( "strings" "testing" + "github.com/samber/lo" "github.com/stretchr/testify/suite" "google.golang.org/grpc/metadata" @@ -63,131 +64,161 @@ func GetContext(ctx context.Context, originValue string) context.Context { func (s *RBACBackupTestSuite) TestBackup() { ctx := GetContext(context.Background(), "root:123456") + + createRole := func(name string) { + resp, err := s.Cluster.Proxy.CreateRole(ctx, &milvuspb.CreateRoleRequest{ + Entity: &milvuspb.RoleEntity{Name: name}, + }) + s.NoError(err) + s.True(merr.Ok(resp)) + } + + operatePrivilege := func(role, privilege, objectName, dbName string, operateType milvuspb.OperatePrivilegeType) { + resp, err := s.Cluster.Proxy.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{ + Type: operateType, + Entity: &milvuspb.GrantEntity{ + Role: &milvuspb.RoleEntity{Name: role}, + Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Collection.String()}, + ObjectName: objectName, + DbName: dbName, + Grantor: &milvuspb.GrantorEntity{User: &milvuspb.UserEntity{Name: util.UserRoot}, Privilege: &milvuspb.PrivilegeEntity{Name: privilege}}, + }, + }) + s.NoError(err) + s.True(merr.Ok(resp)) + } + // test empty rbac content - resp, err := s.Cluster.Proxy.BackupRBAC(ctx, &milvuspb.BackupRBACMetaRequest{}) + emptyBackupRBACResp, err := s.Cluster.Proxy.BackupRBAC(ctx, &milvuspb.BackupRBACMetaRequest{}) s.NoError(err) - s.True(merr.Ok(resp.GetStatus())) - s.Equal("", resp.GetRBACMeta().String()) + s.True(merr.Ok(emptyBackupRBACResp.GetStatus())) + s.Equal("", emptyBackupRBACResp.GetRBACMeta().String()) // generate some rbac content + // create role test_role roleName := "test_role" - resp1, err := s.Cluster.Proxy.CreateRole(ctx, &milvuspb.CreateRoleRequest{ - Entity: &milvuspb.RoleEntity{ - Name: roleName, - }, + createRole(roleName) + + // grant collection level search privilege to role test_role + operatePrivilege(roleName, "Search", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant) + + // create privielge group test_group + groupName := "test_group" + createPrivGroupResp, err := s.Cluster.Proxy.CreatePrivilegeGroup(ctx, &milvuspb.CreatePrivilegeGroupRequest{ + GroupName: groupName, }) s.NoError(err) - s.True(merr.Ok(resp1)) - resp2, err := s.Cluster.Proxy.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{ - Type: milvuspb.OperatePrivilegeType_Grant, - Entity: &milvuspb.GrantEntity{ - Role: &milvuspb.RoleEntity{Name: roleName}, - Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Collection.String()}, - ObjectName: util.AnyWord, - DbName: util.AnyWord, - Grantor: &milvuspb.GrantorEntity{ - User: &milvuspb.UserEntity{Name: util.UserRoot}, - Privilege: &milvuspb.PrivilegeEntity{Name: "Search"}, - }, - }, + s.True(merr.Ok(createPrivGroupResp)) + + // add query and insert privilege to group test_group + addPrivsToGroupResp, err := s.Cluster.Proxy.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{ + GroupName: groupName, + Privileges: []*milvuspb.PrivilegeEntity{{Name: "Query"}, {Name: "Insert"}}, + Type: milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup, }) s.NoError(err) - s.True(merr.Ok(resp2)) - s.Equal("", resp2.GetReason()) + s.True(merr.Ok(addPrivsToGroupResp)) + + // grant privilege group test_group to role test_role + operatePrivilege(roleName, groupName, util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant) + userName := "test_user" passwd := "test_passwd" - resp3, err := s.Cluster.Proxy.CreateCredential(ctx, &milvuspb.CreateCredentialRequest{ + createCredResp, err := s.Cluster.Proxy.CreateCredential(ctx, &milvuspb.CreateCredentialRequest{ Username: userName, Password: crypto.Base64Encode(passwd), }) s.NoError(err) - s.True(merr.Ok(resp3)) - resp4, err := s.Cluster.Proxy.OperateUserRole(ctx, &milvuspb.OperateUserRoleRequest{ + s.True(merr.Ok(createCredResp)) + operateUserRoleResp, err := s.Cluster.Proxy.OperateUserRole(ctx, &milvuspb.OperateUserRoleRequest{ Username: userName, RoleName: roleName, }) s.NoError(err) - s.True(merr.Ok(resp4)) + s.True(merr.Ok(operateUserRoleResp)) - // test back up rbac - resp5, err := s.Cluster.Proxy.BackupRBAC(ctx, &milvuspb.BackupRBACMetaRequest{}) + // test back up rbac, grants should contain + backupRBACResp, err := s.Cluster.Proxy.BackupRBAC(ctx, &milvuspb.BackupRBACMetaRequest{}) s.NoError(err) - s.True(merr.Ok(resp5.GetStatus())) + s.True(merr.Ok(backupRBACResp.GetStatus())) + s.Equal(2, len(backupRBACResp.GetRBACMeta().Grants)) + grants := lo.SliceToMap(backupRBACResp.GetRBACMeta().Grants, func(g *milvuspb.GrantEntity) (string, *milvuspb.GrantEntity) { + return g.Grantor.Privilege.Name, g + }) + s.True(grants["Search"] != nil) + s.True(grants[groupName] != nil) + s.Equal(groupName, backupRBACResp.GetRBACMeta().PrivilegeGroups[0].GroupName) + s.Equal(2, len(backupRBACResp.GetRBACMeta().PrivilegeGroups[0].Privileges)) // test restore, expect to failed due to role/user already exist - resp6, err := s.Cluster.Proxy.RestoreRBAC(ctx, &milvuspb.RestoreRBACMetaRequest{ - RBACMeta: resp5.GetRBACMeta(), + restoreRBACResp, err := s.Cluster.Proxy.RestoreRBAC(ctx, &milvuspb.RestoreRBACMetaRequest{ + RBACMeta: backupRBACResp.GetRBACMeta(), }) s.NoError(err) - s.False(merr.Ok(resp6)) - - // drop exist role/user, successful to restore - resp7, err := s.Cluster.Proxy.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{ - Type: milvuspb.OperatePrivilegeType_Revoke, - Entity: &milvuspb.GrantEntity{ - Role: &milvuspb.RoleEntity{Name: roleName}, - Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Collection.String()}, - ObjectName: util.AnyWord, - DbName: util.AnyWord, - Grantor: &milvuspb.GrantorEntity{ - User: &milvuspb.UserEntity{Name: util.UserRoot}, - Privilege: &milvuspb.PrivilegeEntity{Name: "Search"}, - }, - }, + s.False(merr.Ok(restoreRBACResp)) + + // revoke privilege search from role test_role before dropping the role + operatePrivilege(roleName, "Search", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Revoke) + + // revoke privilege group test_group from role test_role before dropping the role + operatePrivilege(roleName, groupName, util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Revoke) + + // drop privilege group test_group + dropPrivGroupResp, err := s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{ + GroupName: groupName, }) s.NoError(err) - s.True(merr.Ok(resp7)) - resp8, err := s.Cluster.Proxy.DropRole(ctx, &milvuspb.DropRoleRequest{ + s.True(merr.Ok(dropPrivGroupResp)) + + // drop role test_role + dropRoleResp, err := s.Cluster.Proxy.DropRole(ctx, &milvuspb.DropRoleRequest{ RoleName: roleName, }) s.NoError(err) - s.True(merr.Ok(resp8)) - resp9, err := s.Cluster.Proxy.DeleteCredential(ctx, &milvuspb.DeleteCredentialRequest{ + s.True(merr.Ok(dropRoleResp)) + + // delete credential + delCredResp, err := s.Cluster.Proxy.DeleteCredential(ctx, &milvuspb.DeleteCredentialRequest{ Username: userName, }) s.NoError(err) - s.True(merr.Ok(resp9)) + s.True(merr.Ok(delCredResp)) - resp10, err := s.Cluster.Proxy.RestoreRBAC(ctx, &milvuspb.RestoreRBACMetaRequest{ - RBACMeta: resp5.GetRBACMeta(), + // restore rbac + restoreRBACResp, err = s.Cluster.Proxy.RestoreRBAC(ctx, &milvuspb.RestoreRBACMetaRequest{ + RBACMeta: backupRBACResp.GetRBACMeta(), }) s.NoError(err) - s.True(merr.Ok(resp10)) + s.True(merr.Ok(restoreRBACResp)) // check the restored rbac, should be same as the original one - resp11, err := s.Cluster.Proxy.BackupRBAC(ctx, &milvuspb.BackupRBACMetaRequest{}) + backupRBACResp2, err := s.Cluster.Proxy.BackupRBAC(ctx, &milvuspb.BackupRBACMetaRequest{}) s.NoError(err) - s.True(merr.Ok(resp11.GetStatus())) - s.Equal(resp11.GetRBACMeta().String(), resp5.GetRBACMeta().String()) + s.True(merr.Ok(backupRBACResp2.GetStatus())) + s.Equal(backupRBACResp2.GetRBACMeta().String(), backupRBACResp.GetRBACMeta().String()) // clean rbac meta - resp12, err := s.Cluster.Proxy.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{ - Type: milvuspb.OperatePrivilegeType_Revoke, - Entity: &milvuspb.GrantEntity{ - Role: &milvuspb.RoleEntity{Name: roleName}, - Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Collection.String()}, - ObjectName: util.AnyWord, - DbName: util.AnyWord, - Grantor: &milvuspb.GrantorEntity{ - User: &milvuspb.UserEntity{Name: util.UserRoot}, - Privilege: &milvuspb.PrivilegeEntity{Name: "Search"}, - }, - }, + operatePrivilege(roleName, "Search", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Revoke) + + operatePrivilege(roleName, groupName, util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Revoke) + + dropPrivGroupResp2, err := s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{ + GroupName: groupName, }) s.NoError(err) - s.True(merr.Ok(resp12)) + s.True(merr.Ok(dropPrivGroupResp2)) - resp13, err := s.Cluster.Proxy.DropRole(ctx, &milvuspb.DropRoleRequest{ + dropRoleResp2, err := s.Cluster.Proxy.DropRole(ctx, &milvuspb.DropRoleRequest{ RoleName: roleName, }) s.NoError(err) - s.True(merr.Ok(resp13)) + s.True(merr.Ok(dropRoleResp2)) - resp14, err := s.Cluster.Proxy.DeleteCredential(ctx, &milvuspb.DeleteCredentialRequest{ + delCredResp2, err := s.Cluster.Proxy.DeleteCredential(ctx, &milvuspb.DeleteCredentialRequest{ Username: userName, }) s.NoError(err) - s.True(merr.Ok(resp14)) + s.True(merr.Ok(delCredResp2)) } func TestRBACBackup(t *testing.T) { From 48c3a5ded1cc29d35eb0b7d1767a79dc39a01286 Mon Sep 17 00:00:00 2001 From: shaoting-huang Date: Mon, 11 Nov 2024 11:18:19 +0800 Subject: [PATCH 2/2] update proto Signed-off-by: shaoting-huang --- go.mod | 1 - go.sum | 2 -- pkg/go.mod | 2 +- pkg/go.sum | 4 ++-- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 26800de17a7b3..d2f274bcf773f 100644 --- a/go.mod +++ b/go.mod @@ -153,7 +153,6 @@ require ( github.com/mattn/go-colorable v0.1.11 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/milvus-io/milvus-proto v2.2.9+incompatible // indirect github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect github.com/minio/highwayhash v1.0.2 // indirect diff --git a/go.sum b/go.sum index 191d54286ab29..06f4622fe0917 100644 --- a/go.sum +++ b/go.sum @@ -608,8 +608,6 @@ github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119 h1:9VXijWu github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg= github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b h1:TfeY0NxYxZzUfIfYe5qYDBzt4ZYRqzUjTR6CvUzjat8= github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b/go.mod h1:iwW+9cWfIzzDseEBCCeDSN5SD16Tidvy8cwQ7ZY8Qj4= -github.com/milvus-io/milvus-proto v2.2.9+incompatible h1:PYDvPc2U4yJoQyTjuFctGT5QSh7hcLyoQJUGQHhI+lY= -github.com/milvus-io/milvus-proto v2.2.9+incompatible/go.mod h1:urRUo4TMLWgew3AYu4Ak1oYr7KcV6AO2momWggKrDIw= github.com/milvus-io/milvus-proto/go-api/v2 v2.4.16-0.20241110064419-549e4694a7e7 h1:gq5xxDS2EIYVk3ujO+sQgDWrhTTpsmV+r6Gm7dfFrt8= github.com/milvus-io/milvus-proto/go-api/v2 v2.4.16-0.20241110064419-549e4694a7e7/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= github.com/milvus-io/milvus-storage/go v0.0.0-20231227072638-ebd0b8e56d70 h1:Z+sp64fmAOxAG7mU0dfVOXvAXlwRB0c8a96rIM5HevI= diff --git a/pkg/go.mod b/pkg/go.mod index b3fa9deb7ac49..17192fed58738 100644 --- a/pkg/go.mod +++ b/pkg/go.mod @@ -12,7 +12,7 @@ require ( github.com/expr-lang/expr v1.15.7 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/klauspost/compress v1.17.7 - github.com/milvus-io/milvus-proto/go-api/v2 v2.4.15 + github.com/milvus-io/milvus-proto/go-api/v2 v2.4.16-0.20241110064419-549e4694a7e7 github.com/nats-io/nats-server/v2 v2.10.12 github.com/nats-io/nats.go v1.34.1 github.com/panjf2000/ants/v2 v2.7.2 diff --git a/pkg/go.sum b/pkg/go.sum index c41940317fa8b..3daf8119ee54c 100644 --- a/pkg/go.sum +++ b/pkg/go.sum @@ -503,8 +503,8 @@ github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119 h1:9VXijWu github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg= github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b h1:TfeY0NxYxZzUfIfYe5qYDBzt4ZYRqzUjTR6CvUzjat8= github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b/go.mod h1:iwW+9cWfIzzDseEBCCeDSN5SD16Tidvy8cwQ7ZY8Qj4= -github.com/milvus-io/milvus-proto/go-api/v2 v2.4.15 h1:1y+hkeGh7zaD5ZasWjfKNZYWdH8VlLKcjoeyFiSh/I8= -github.com/milvus-io/milvus-proto/go-api/v2 v2.4.15/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= +github.com/milvus-io/milvus-proto/go-api/v2 v2.4.16-0.20241110064419-549e4694a7e7 h1:gq5xxDS2EIYVk3ujO+sQgDWrhTTpsmV+r6Gm7dfFrt8= +github.com/milvus-io/milvus-proto/go-api/v2 v2.4.16-0.20241110064419-549e4694a7e7/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= github.com/milvus-io/pulsar-client-go v0.6.10 h1:eqpJjU+/QX0iIhEo3nhOqMNXL+TyInAs1IAHZCrCM/A= github.com/milvus-io/pulsar-client-go v0.6.10/go.mod h1:lQqCkgwDF8YFYjKA+zOheTk1tev2B+bKj5j7+nm8M1w= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=