Implement the mysql metastore of the rbac (#18704)

Signed-off-by: SimFG <bang.fu@zilliz.com>

Signed-off-by: SimFG <bang.fu@zilliz.com>
pull/18769/head
SimFG 2022-08-23 10:26:53 +08:00 committed by GitHub
parent 94ffa8a275
commit ce434b496e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 2628 additions and 168 deletions

View File

@ -55,7 +55,7 @@ metastore:
# Related configuration of mysql, used to store Milvus metadata.
mysql:
username: root
password: 11111111
password: 123456
address: localhost
port: 3306
dbName: milvus_meta

View File

@ -38,11 +38,11 @@ type RootCoordCatalog interface {
CreateRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity) error
DropRole(ctx context.Context, tenant string, roleName string) error
OperateUserRole(ctx context.Context, tenant string, userEntity *milvuspb.UserEntity, roleEntity *milvuspb.RoleEntity, operateType milvuspb.OperateUserRoleType) error
SelectRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error)
SelectUser(ctx context.Context, tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error)
OperatePrivilege(ctx context.Context, tenant string, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error
SelectGrant(ctx context.Context, tenant string, entity *milvuspb.GrantEntity) ([]*milvuspb.GrantEntity, error)
AlterUserRole(ctx context.Context, tenant string, userEntity *milvuspb.UserEntity, roleEntity *milvuspb.RoleEntity, operateType milvuspb.OperateUserRoleType) error
ListRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error)
ListUser(ctx context.Context, tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error)
AlterGrant(ctx context.Context, tenant string, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error
ListGrant(ctx context.Context, tenant string, entity *milvuspb.GrantEntity) ([]*milvuspb.GrantEntity, error)
ListPolicy(ctx context.Context, tenant string) ([]string, error)
ListUserRole(ctx context.Context, tenant string) ([]string, error)

View File

@ -44,6 +44,9 @@ var (
indexTestDb dbmodel.IIndexDb
segIndexTestDb dbmodel.ISegmentIndexDb
userTestDb dbmodel.IUserDb
roleTestDb dbmodel.IRoleDb
userRoleTestDb dbmodel.IUserRoleDb
grantTestDb dbmodel.IGrantDb
)
// TestMain is the first function executed in current package, we will do some initial here
@ -51,6 +54,7 @@ func TestMain(m *testing.M) {
var (
db *sql.DB
err error
ctx = context.TODO()
)
// setting sql MUST exact match
@ -70,14 +74,17 @@ func TestMain(m *testing.M) {
// set mocked database
dbcore.SetGlobalDB(DB)
collTestDb = NewMetaDomain().CollectionDb(context.TODO())
aliasTestDb = NewMetaDomain().CollAliasDb(context.TODO())
channelTestDb = NewMetaDomain().CollChannelDb(context.TODO())
fieldTestDb = NewMetaDomain().FieldDb(context.TODO())
partitionTestDb = NewMetaDomain().PartitionDb(context.TODO())
indexTestDb = NewMetaDomain().IndexDb(context.TODO())
segIndexTestDb = NewMetaDomain().SegmentIndexDb(context.TODO())
userTestDb = NewMetaDomain().UserDb(context.TODO())
collTestDb = NewMetaDomain().CollectionDb(ctx)
aliasTestDb = NewMetaDomain().CollAliasDb(ctx)
channelTestDb = NewMetaDomain().CollChannelDb(ctx)
fieldTestDb = NewMetaDomain().FieldDb(ctx)
partitionTestDb = NewMetaDomain().PartitionDb(ctx)
indexTestDb = NewMetaDomain().IndexDb(ctx)
segIndexTestDb = NewMetaDomain().SegmentIndexDb(ctx)
userTestDb = NewMetaDomain().UserDb(ctx)
roleTestDb = NewMetaDomain().RoleDb(ctx)
userRoleTestDb = NewMetaDomain().UserRoleDb(ctx)
grantTestDb = NewMetaDomain().GrantDb(ctx)
// m.Run entry for executing tests
os.Exit(m.Run())
@ -392,3 +399,24 @@ func (a AnyTime) Match(v driver.Value) bool {
_, ok := v.(time.Time)
return ok
}
func GetBase() dbmodel.Base {
return dbmodel.Base{
TenantID: tenantID,
IsDeleted: false,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
}
func SuccessExec(f func()) {
mock.ExpectBegin()
f()
mock.ExpectCommit()
}
func ErrorExec(f func()) {
mock.ExpectBegin()
f()
mock.ExpectRollback()
}

View File

@ -44,3 +44,15 @@ func (*metaDomain) SegmentIndexDb(ctx context.Context) dbmodel.ISegmentIndexDb {
func (*metaDomain) UserDb(ctx context.Context) dbmodel.IUserDb {
return &userDb{dbcore.GetDB(ctx)}
}
func (d *metaDomain) RoleDb(ctx context.Context) dbmodel.IRoleDb {
return &roleDb{dbcore.GetDB(ctx)}
}
func (d *metaDomain) UserRoleDb(ctx context.Context) dbmodel.IUserRoleDb {
return &userRoleDb{dbcore.GetDB(ctx)}
}
func (d *metaDomain) GrantDb(ctx context.Context) dbmodel.IGrantDb {
return &grantDb{dbcore.GetDB(ctx)}
}

View File

@ -0,0 +1,93 @@
package dao
import (
"errors"
"fmt"
"github.com/milvus-io/milvus/internal/common"
"github.com/milvus-io/milvus/internal/log"
"github.com/milvus-io/milvus/internal/metastore/db/dbmodel"
"go.uber.org/zap"
"gorm.io/gorm"
)
type grantDb struct {
db *gorm.DB
}
func (g *grantDb) GetGrants(tenantID string, roleID int64, object string, objectName string) ([]*dbmodel.Grant, error) {
var (
grants []*dbmodel.Grant
err error
)
err = g.db.Model(&dbmodel.Grant{}).Where(&dbmodel.Grant{RoleID: roleID, Object: object, ObjectName: objectName}).Where(dbmodel.GetCommonCondition(tenantID, false)).Preload("Role").Find(&grants).Error
if err != nil {
log.Error("fail to get grants", zap.String("tenant_id", tenantID), zap.Int64("roleID", roleID), zap.String("object", object), zap.String("object_name", objectName), zap.Error(err))
return nil, err
}
return grants, nil
}
func (g *grantDb) Insert(in *dbmodel.Grant) error {
var (
sqlWhere = &dbmodel.Grant{RoleID: in.RoleID, Object: in.Object, ObjectName: in.ObjectName}
dbGrant *dbmodel.Grant
newDbDetail string
err error
)
err = g.db.Model(&dbmodel.Grant{}).Where(sqlWhere).Where(dbmodel.GetCommonCondition(in.TenantID, false)).Take(&dbGrant).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
err = g.db.Create(in).Error
if err != nil {
log.Error("fail to insert the grant", zap.Any("in", in), zap.Error(err))
}
return err
}
if err != nil {
log.Error("fail to take the origin grant", zap.Any("in", in), zap.Error(err))
return err
}
if newDbDetail, err = dbmodel.EncodeGrantDetailForString(dbGrant.Detail, in.Detail, true); err != nil {
log.Error("fail to encode the grant detail", zap.Any("in", in), zap.Error(err))
return err
}
err = g.db.Model(dbmodel.Grant{}).Where(sqlWhere).Where(dbmodel.GetCommonCondition(in.TenantID, false)).Update("detail", newDbDetail).Error
if err != nil {
log.Error("fail to update the grant", zap.Any("in", in), zap.Error(err))
}
return err
}
func (g *grantDb) Delete(tenantID string, roleID int64, object string, objectName string, privilege string) error {
var (
sqlWhere = &dbmodel.Grant{RoleID: roleID, Object: object, ObjectName: objectName}
dbGrant *dbmodel.Grant
newDbDetail string
db *gorm.DB
err error
)
err = g.db.Model(&dbmodel.Grant{}).Where(sqlWhere).Where(dbmodel.GetCommonCondition(tenantID, false)).Take(&dbGrant).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return common.NewIgnorableError(fmt.Errorf("the privilege[%s-%s-%s] isn't granted", object, objectName, privilege))
}
if err != nil {
log.Error("fail to take the origin grant", zap.Any("where", sqlWhere), zap.Error(err))
return err
}
if newDbDetail, err = dbmodel.EncodeGrantDetail(dbGrant.Detail, "", privilege, false); err != nil {
log.Error("fail to encode the grant detail", zap.Any("detail", dbGrant.Detail), zap.String("privilege", privilege), zap.Error(err))
return err
}
db = g.db.Model(dbmodel.Grant{}).Where(sqlWhere).Where(dbmodel.GetCommonCondition(tenantID, false))
if newDbDetail == "" {
err = db.Update("is_deleted", true).Error
} else {
err = db.Update("detail", newDbDetail).Error
}
if err != nil {
log.Error("fail to delete the grant", zap.Bool("is_delete", newDbDetail == ""), zap.Any("where", sqlWhere), zap.String("privilege", privilege), zap.Error(err))
}
return err
}

View File

@ -0,0 +1,419 @@
package dao
import (
"errors"
"testing"
"github.com/milvus-io/milvus/internal/common"
"gorm.io/gorm"
"github.com/DATA-DOG/go-sqlmock"
"github.com/milvus-io/milvus/internal/metastore/db/dbmodel"
"github.com/stretchr/testify/assert"
)
func TestGrant_GetGrants(t *testing.T) {
var (
roleID1 = 10
roleID2 = 20
object = "Collection"
objectName = "col1"
grants []*dbmodel.Grant
getQuery func() *sqlmock.ExpectedQuery
err error
)
getQuery = func() *sqlmock.ExpectedQuery {
return mock.ExpectQuery("SELECT * FROM `grant` WHERE `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(false, tenantID)
}
getQuery().WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "role_id", "object", "object_name"}).
AddRow(tenantID, roleID1, object, objectName).
AddRow(tenantID, roleID2, object, objectName))
mock.ExpectQuery("SELECT * FROM `role` WHERE `role`.`id` IN (?,?)").
WithArgs(roleID1, roleID2).
WillReturnRows(
sqlmock.NewRows([]string{"id", "tenant_id", "name"}).
AddRow(roleID1, tenantID, "foo1").
AddRow(roleID2, tenantID, "foo2"))
grants, err = grantTestDb.GetGrants(tenantID, 0, "", "")
assert.NoError(t, err)
assert.Equal(t, 2, len(grants))
assert.Equal(t, "foo2", grants[1].Role.Name)
assert.Equal(t, object, grants[0].Object)
assert.Equal(t, objectName, grants[0].ObjectName)
getQuery().WillReturnError(errors.New("test error"))
_, err = grantTestDb.GetGrants(tenantID, 0, "", "")
assert.Error(t, err)
}
func TestGrant_GetGrantsWithRoleID(t *testing.T) {
var (
roleID1 = 10
object1 = "Collection"
objectName1 = "col1"
object2 = "Global"
objectName2 = "*"
grants []*dbmodel.Grant
getQuery func() *sqlmock.ExpectedQuery
err error
)
getQuery = func() *sqlmock.ExpectedQuery {
return mock.ExpectQuery("SELECT * FROM `grant` WHERE `grant`.`role_id` = ? AND `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(roleID1, false, tenantID)
}
getQuery().WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "role_id", "object", "object_name"}).
AddRow(tenantID, roleID1, object1, objectName1).
AddRow(tenantID, roleID1, object2, objectName2))
mock.ExpectQuery("SELECT * FROM `role` WHERE `role`.`id` = ?").
WithArgs(roleID1).
WillReturnRows(
sqlmock.NewRows([]string{"id", "tenant_id", "name"}).
AddRow(roleID1, tenantID, "foo1"))
grants, err = grantTestDb.GetGrants(tenantID, int64(roleID1), "", "")
assert.NoError(t, err)
assert.Equal(t, 2, len(grants))
assert.Equal(t, "foo1", grants[0].Role.Name)
assert.Equal(t, object1, grants[0].Object)
assert.Equal(t, objectName2, grants[1].ObjectName)
}
func TestGrant_GetGrantsWithObject(t *testing.T) {
var (
roleID = 10
object = "Collection"
objectName = "col1"
detail1 = "privilege1..."
detail2 = "privilege2..."
grants []*dbmodel.Grant
getQuery func() *sqlmock.ExpectedQuery
err error
)
getQuery = func() *sqlmock.ExpectedQuery {
return mock.ExpectQuery("SELECT * FROM `grant` WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(roleID, object, objectName, false, tenantID)
}
getQuery().WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "role_id", "object", "object_name", "detail"}).
AddRow(tenantID, roleID, object, objectName, detail1).
AddRow(tenantID, roleID, object, objectName, detail2))
mock.ExpectQuery("SELECT * FROM `role` WHERE `role`.`id` = ?").
WithArgs(roleID).
WillReturnRows(
sqlmock.NewRows([]string{"id", "tenant_id", "name"}).
AddRow(roleID, tenantID, "foo1"))
grants, err = grantTestDb.GetGrants(tenantID, int64(roleID), object, objectName)
assert.NoError(t, err)
assert.Equal(t, 2, len(grants))
assert.Equal(t, "foo1", grants[0].Role.Name)
assert.Equal(t, object, grants[1].Object)
assert.Equal(t, objectName, grants[1].ObjectName)
assert.Equal(t, detail2, grants[1].Detail)
}
func TestGrant_Insert(t *testing.T) {
var (
grant *dbmodel.Grant
err error
)
grant = &dbmodel.Grant{
Base: GetBase(),
RoleID: 1,
Object: "Global",
ObjectName: "Col",
Detail: "[[\"admin\",\"PrivilegeIndexDetail1\"]]",
}
mock.ExpectQuery("SELECT * FROM `grant` WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ? LIMIT 1").
WithArgs(grant.RoleID, grant.Object, grant.ObjectName, grant.IsDeleted, grant.TenantID).
WillReturnError(gorm.ErrRecordNotFound)
mock.ExpectBegin()
mock.ExpectExec("INSERT INTO `grant` (`tenant_id`,`is_deleted`,`created_at`,`updated_at`,`role_id`,`object`,`object_name`,`detail`) VALUES (?,?,?,?,?,?,?,?)").
WithArgs(grant.TenantID, grant.IsDeleted, grant.CreatedAt, grant.UpdatedAt, grant.RoleID, grant.Object, grant.ObjectName, grant.Detail).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
err = grantTestDb.Insert(grant)
assert.NoError(t, err)
}
func TestGrant_Insert_Error(t *testing.T) {
var (
grant *dbmodel.Grant
err error
)
grant = &dbmodel.Grant{
Base: GetBase(),
RoleID: 1,
Object: "Global",
ObjectName: "Col",
Detail: "[[\"admin\",\"PrivilegeIndexDetail2\"]]",
}
mock.ExpectQuery("SELECT * FROM `grant` WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ? LIMIT 1").
WithArgs(grant.RoleID, grant.Object, grant.ObjectName, grant.IsDeleted, grant.TenantID).
WillReturnError(gorm.ErrRecordNotFound)
mock.ExpectBegin()
mock.ExpectExec("INSERT INTO `grant` (`tenant_id`,`is_deleted`,`created_at`,`updated_at`,`role_id`,`object`,`object_name`,`detail`) VALUES (?,?,?,?,?,?,?,?)").
WithArgs(grant.TenantID, grant.IsDeleted, grant.CreatedAt, grant.UpdatedAt, grant.RoleID, grant.Object, grant.ObjectName, grant.Detail).
WillReturnError(errors.New("test error"))
mock.ExpectRollback()
err = grantTestDb.Insert(grant)
assert.Error(t, err)
}
func TestGrant_Insert_SelectError(t *testing.T) {
var (
grant *dbmodel.Grant
err error
)
grant = &dbmodel.Grant{
Base: GetBase(),
RoleID: 1,
Object: "Global",
ObjectName: "Col",
Detail: "[[\"admin\",\"PrivilegeIndexDetail3\"]]",
}
mock.ExpectQuery("SELECT * FROM `grant` WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ? LIMIT 1").
WithArgs(grant.RoleID, grant.Object, grant.ObjectName, grant.IsDeleted, grant.TenantID).
WillReturnError(errors.New("test error"))
err = grantTestDb.Insert(grant)
assert.Error(t, err)
}
func TestGrant_InsertDecode(t *testing.T) {
var (
grant *dbmodel.Grant
err error
)
grant = &dbmodel.Grant{
Base: GetBase(),
RoleID: 1,
Object: "Global",
ObjectName: "Col",
Detail: "[[\"admin\",\"PrivilegeIndexDetail\"]]",
}
mock.ExpectQuery("SELECT * FROM `grant` WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ? LIMIT 1").
WithArgs(grant.RoleID, grant.Object, grant.ObjectName, grant.IsDeleted, grant.TenantID).
WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "role_id", "object", "object_name", "detail"}).
AddRow(tenantID, grant.RoleID, grant.Object, grant.ObjectName, "aaa"))
err = grantTestDb.Insert(grant)
assert.Error(t, err)
}
func TestGrant_InsertUpdate(t *testing.T) {
var (
originDetail = "[[\"admin\",\"PrivilegeLoad\"]]"
grant *dbmodel.Grant
expectDetail string
err error
)
grant = &dbmodel.Grant{
Base: GetBase(),
RoleID: 1,
Object: "Global",
ObjectName: "Col",
Detail: "[[\"admin\",\"PrivilegeIndexDetail\"]]",
}
expectDetail, _ = dbmodel.EncodeGrantDetailForString(originDetail, grant.Detail, true)
mock.ExpectQuery("SELECT * FROM `grant` WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ? LIMIT 1").
WithArgs(grant.RoleID, grant.Object, grant.ObjectName, grant.IsDeleted, grant.TenantID).
WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "role_id", "object", "object_name", "detail"}).
AddRow(tenantID, grant.RoleID, grant.Object, grant.ObjectName, originDetail))
mock.ExpectBegin()
mock.ExpectExec("UPDATE `grant` SET `detail`=?,`updated_at`=? WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(expectDetail, AnyTime{}, grant.RoleID, grant.Object, grant.ObjectName, grant.IsDeleted, grant.TenantID).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
err = grantTestDb.Insert(grant)
assert.NoError(t, err)
}
func TestGrant_InsertUpdateError(t *testing.T) {
var (
originDetail = "[[\"admin\",\"PrivilegeIndexDetail\"]]"
grant *dbmodel.Grant
err error
)
grant = &dbmodel.Grant{
Base: GetBase(),
RoleID: 1,
Object: "Global",
ObjectName: "Col",
Detail: "[[\"admin\",\"PrivilegeIndexDetail\"]]",
}
mock.ExpectQuery("SELECT * FROM `grant` WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ? LIMIT 1").
WithArgs(grant.RoleID, grant.Object, grant.ObjectName, grant.IsDeleted, grant.TenantID).
WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "role_id", "object", "object_name", "detail"}).
AddRow(tenantID, grant.RoleID, grant.Object, grant.ObjectName, originDetail))
err = grantTestDb.Insert(grant)
assert.Error(t, err)
assert.True(t, common.IsIgnorableError(err))
}
func TestGrant_DeleteWithoutPrivilege(t *testing.T) {
var (
grant *dbmodel.Grant
privilege = "PrivilegeIndexDetail"
err error
)
grant = &dbmodel.Grant{
Base: GetBase(),
RoleID: 1,
Object: "Global",
ObjectName: "Col",
Detail: "[[\"admin\",\"PrivilegeIndexDetail\"]]",
}
mock.ExpectQuery("SELECT * FROM `grant` WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ? LIMIT 1").
WithArgs(grant.RoleID, grant.Object, grant.ObjectName, grant.IsDeleted, grant.TenantID).
WillReturnError(gorm.ErrRecordNotFound)
err = grantTestDb.Delete(grant.TenantID, grant.RoleID, grant.Object, grant.ObjectName, privilege)
assert.Error(t, err)
assert.True(t, common.IsIgnorableError(err))
}
func TestGrant_Delete_GetError(t *testing.T) {
var (
grant *dbmodel.Grant
privilege = "PrivilegeIndexDetail"
err error
)
grant = &dbmodel.Grant{
Base: GetBase(),
RoleID: 1,
Object: "Global",
ObjectName: "Col",
Detail: "[[\"admin\",\"PrivilegeIndexDetail\"]]",
}
mock.ExpectQuery("SELECT * FROM `grant` WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ? LIMIT 1").
WithArgs(grant.RoleID, grant.Object, grant.ObjectName, grant.IsDeleted, grant.TenantID).
WillReturnError(errors.New("test error"))
err = grantTestDb.Delete(grant.TenantID, grant.RoleID, grant.Object, grant.ObjectName, privilege)
assert.Error(t, err)
}
func TestGrant_Delete_DecodeError(t *testing.T) {
var (
grant *dbmodel.Grant
privilege = "PrivilegeIndexDetail"
err error
)
grant = &dbmodel.Grant{
Base: GetBase(),
RoleID: 1,
Object: "Global",
ObjectName: "Col",
Detail: "[[\"admin\",\"PrivilegeIndexDetail\"]]",
}
mock.ExpectQuery("SELECT * FROM `grant` WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ? LIMIT 1").
WithArgs(grant.RoleID, grant.Object, grant.ObjectName, grant.IsDeleted, grant.TenantID).
WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "role_id", "object", "object_name", "detail"}).
AddRow(tenantID, grant.RoleID, grant.Object, grant.ObjectName, "aaa"))
err = grantTestDb.Delete(grant.TenantID, grant.RoleID, grant.Object, grant.ObjectName, privilege)
assert.Error(t, err)
}
func TestGrant_Delete_Mark(t *testing.T) {
var (
grant *dbmodel.Grant
privilege = "PrivilegeIndexDetail"
err error
)
grant = &dbmodel.Grant{
Base: GetBase(),
RoleID: 1,
Object: "Global",
ObjectName: "Col",
Detail: "[[\"admin\",\"PrivilegeIndexDetail\"]]",
}
mock.ExpectQuery("SELECT * FROM `grant` WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ? LIMIT 1").
WithArgs(grant.RoleID, grant.Object, grant.ObjectName, grant.IsDeleted, grant.TenantID).
WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "role_id", "object", "object_name", "detail"}).
AddRow(tenantID, grant.RoleID, grant.Object, grant.ObjectName, grant.Detail))
mock.ExpectBegin()
mock.ExpectExec("UPDATE `grant` SET `is_deleted`=?,`updated_at`=? WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(true, AnyTime{}, grant.RoleID, grant.Object, grant.ObjectName, false, grant.TenantID).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
err = grantTestDb.Delete(grant.TenantID, grant.RoleID, grant.Object, grant.ObjectName, privilege)
assert.NoError(t, err)
}
func TestGrant_Delete_Update(t *testing.T) {
var (
grant *dbmodel.Grant
privilege = "PrivilegeIndexDetail"
expectDetail = "[[\"admin\",\"PrivilegeLoad\"]]"
err error
)
grant = &dbmodel.Grant{
Base: GetBase(),
RoleID: 1,
Object: "Global",
ObjectName: "Col",
Detail: "[[\"admin\",\"PrivilegeIndexDetail\"],[\"admin\",\"PrivilegeLoad\"]]",
}
mock.ExpectQuery("SELECT * FROM `grant` WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ? LIMIT 1").
WithArgs(grant.RoleID, grant.Object, grant.ObjectName, grant.IsDeleted, grant.TenantID).
WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "role_id", "object", "object_name", "detail"}).
AddRow(tenantID, grant.RoleID, grant.Object, grant.ObjectName, grant.Detail))
mock.ExpectBegin()
mock.ExpectExec("UPDATE `grant` SET `detail`=?,`updated_at`=? WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(expectDetail, AnyTime{}, grant.RoleID, grant.Object, grant.ObjectName, false, grant.TenantID).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
err = grantTestDb.Delete(grant.TenantID, grant.RoleID, grant.Object, grant.ObjectName, privilege)
assert.NoError(t, err)
}
func TestGrant_Delete_UpdateError(t *testing.T) {
var (
grant *dbmodel.Grant
privilege = "PrivilegeIndexDetail"
err error
)
grant = &dbmodel.Grant{
Base: GetBase(),
RoleID: 1,
Object: "Global",
ObjectName: "Col",
Detail: "[[\"admin\",\"PrivilegeIndexLoad\"],[\"admin\",\"PrivilegeQuery\"]]",
}
mock.ExpectQuery("SELECT * FROM `grant` WHERE `grant`.`role_id` = ? AND `grant`.`object` = ? AND `grant`.`object_name` = ? AND `is_deleted` = ? AND `tenant_id` = ? LIMIT 1").
WithArgs(grant.RoleID, grant.Object, grant.ObjectName, grant.IsDeleted, grant.TenantID).
WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "role_id", "object", "object_name", "detail"}).
AddRow(tenantID, grant.RoleID, grant.Object, grant.ObjectName, grant.Detail))
err = grantTestDb.Delete(grant.TenantID, grant.RoleID, grant.Object, grant.ObjectName, privilege)
assert.Error(t, err)
assert.True(t, common.IsIgnorableError(err))
}

View File

@ -0,0 +1,41 @@
package dao
import (
"github.com/milvus-io/milvus/internal/log"
"github.com/milvus-io/milvus/internal/metastore/db/dbmodel"
"go.uber.org/zap"
"gorm.io/gorm"
)
type roleDb struct {
db *gorm.DB
}
func (r *roleDb) GetRoles(tenantID string, name string) ([]*dbmodel.Role, error) {
var (
roles []*dbmodel.Role
err error
)
err = r.db.Model(&dbmodel.Role{}).Where(&dbmodel.Role{Name: name}).Where(dbmodel.GetCommonCondition(tenantID, false)).Find(&roles).Error
if err != nil {
log.Error("fail to get roles", zap.String("tenant_id", tenantID), zap.String("name", name), zap.Error(err))
return nil, err
}
return roles, nil
}
func (r *roleDb) Insert(in *dbmodel.Role) error {
err := r.db.Create(in).Error
if err != nil {
log.Error("fail to insert the role", zap.Any("in", in), zap.Error(err))
}
return err
}
func (r *roleDb) Delete(tenantID string, name string) error {
err := r.db.Model(dbmodel.Role{}).Where(&dbmodel.Role{Name: name}).Where(dbmodel.GetCommonCondition(tenantID, false)).Update("is_deleted", true).Error
if err != nil {
log.Error("fail to delete the role", zap.String("tenant_id", tenantID), zap.String("name", name), zap.Error(err))
}
return err
}

View File

@ -0,0 +1,132 @@
package dao
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/milvus-io/milvus/internal/metastore/db/dbmodel"
"github.com/DATA-DOG/go-sqlmock"
)
func TestRole_GetRoles(t *testing.T) {
var (
roles []*dbmodel.Role
err error
)
mock.ExpectQuery("SELECT * FROM `role` WHERE `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(false, tenantID).
WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "name"}).
AddRow(tenantID, "foo1").
AddRow(tenantID, "foo2"))
roles, err = roleTestDb.GetRoles(tenantID, "")
assert.NoError(t, err)
assert.Equal(t, 2, len(roles))
}
func TestRole_GetRoles_Error(t *testing.T) {
mock.ExpectQuery("SELECT * FROM `role` WHERE `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(false, tenantID).
WillReturnError(errors.New("test error"))
_, err := roleTestDb.GetRoles(tenantID, "")
assert.Error(t, err)
}
func TestRole_GetRoles_WithRoleName(t *testing.T) {
var (
roleName = "foo1"
roles []*dbmodel.Role
err error
)
mock.ExpectQuery("SELECT * FROM `role` WHERE `role`.`name` = ? AND `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(roleName, false, tenantID).
WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "name"}).
AddRow(tenantID, roleName))
roles, err = roleTestDb.GetRoles(tenantID, roleName)
assert.NoError(t, err)
assert.Equal(t, 1, len(roles))
assert.Equal(t, roleName, roles[0].Name)
}
func TestRole_Insert(t *testing.T) {
var (
role *dbmodel.Role
err error
)
role = &dbmodel.Role{
Base: GetBase(),
Name: "foo",
}
mock.ExpectBegin()
mock.ExpectExec("INSERT INTO `role` (`tenant_id`,`is_deleted`,`created_at`,`updated_at`,`name`) VALUES (?,?,?,?,?)").
WithArgs(role.TenantID, role.IsDeleted, role.CreatedAt, role.UpdatedAt, role.Name).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
err = roleTestDb.Insert(role)
assert.NoError(t, err)
}
func TestRole_Insert_Error(t *testing.T) {
var (
role *dbmodel.Role
err error
)
role = &dbmodel.Role{
Base: GetBase(),
Name: "foo",
}
mock.ExpectBegin()
mock.ExpectExec("INSERT INTO `role` (`tenant_id`,`is_deleted`,`created_at`,`updated_at`,`name`) VALUES (?,?,?,?,?)").
WithArgs(role.TenantID, role.IsDeleted, role.CreatedAt, role.UpdatedAt, role.Name).
WillReturnError(errors.New("test error"))
mock.ExpectRollback()
err = roleTestDb.Insert(role)
assert.Error(t, err)
}
func TestRole_Delete(t *testing.T) {
var (
role *dbmodel.Role
err error
)
role = &dbmodel.Role{
Base: GetBase(),
Name: "foo",
}
mock.ExpectBegin()
mock.ExpectExec("UPDATE `role` SET `is_deleted`=?,`updated_at`=? WHERE `role`.`name` = ? AND `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(true, AnyTime{}, role.Name, role.IsDeleted, role.TenantID).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
err = roleTestDb.Delete(role.TenantID, role.Name)
assert.NoError(t, err)
}
func TestRole_Delete_Error(t *testing.T) {
var (
role *dbmodel.Role
err error
)
role = &dbmodel.Role{
Base: GetBase(),
Name: "foo",
}
mock.ExpectBegin()
mock.ExpectExec("UPDATE `role` SET `is_deleted`=?,`updated_at`=? WHERE `role`.`name` = ? AND `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(true, AnyTime{}, role.Name, role.IsDeleted, role.TenantID).
WillReturnError(errors.New("test error"))
mock.ExpectRollback()
err = roleTestDb.Delete(role.TenantID, role.Name)
assert.Error(t, err)
}

View File

@ -4,6 +4,8 @@ import (
"errors"
"fmt"
"github.com/milvus-io/milvus/internal/common"
"github.com/milvus-io/milvus/internal/log"
"github.com/milvus-io/milvus/internal/metastore/db/dbmodel"
"go.uber.org/zap"
@ -20,7 +22,7 @@ func (s *userDb) GetByUsername(tenantID string, username string) (*dbmodel.User,
err := s.db.Model(&dbmodel.User{}).Where("tenant_id = ? AND username = ? AND is_deleted = false", tenantID, username).Take(&r).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("user %s not found", username)
return nil, common.NewKeyNotExistError(fmt.Sprintf("%s/%s", tenantID, username))
}
if err != nil {
log.Error("get user by username failed", zap.String("tenant", tenantID), zap.String("username", username), zap.Error(err))
@ -30,16 +32,16 @@ func (s *userDb) GetByUsername(tenantID string, username string) (*dbmodel.User,
return r, nil
}
func (s *userDb) ListUsername(tenantID string) ([]string, error) {
var usernames []string
func (s *userDb) ListUser(tenantID string) ([]*dbmodel.User, error) {
var users []*dbmodel.User
err := s.db.Model(&dbmodel.User{}).Select("username").Where("tenant_id = ? AND is_deleted = false", tenantID).Find(&usernames).Error
err := s.db.Model(&dbmodel.User{}).Where("tenant_id = ? AND is_deleted = false", tenantID).Find(&users).Error
if err != nil {
log.Error("list usernames failed", zap.String("tenant", tenantID), zap.Error(err))
log.Error("list user failed", zap.String("tenant", tenantID), zap.Error(err))
return nil, err
}
return usernames, nil
return users, nil
}
func (s *userDb) Insert(in *dbmodel.User) error {

View File

@ -0,0 +1,41 @@
package dao
import (
"github.com/milvus-io/milvus/internal/log"
"github.com/milvus-io/milvus/internal/metastore/db/dbmodel"
"go.uber.org/zap"
"gorm.io/gorm"
)
type userRoleDb struct {
db *gorm.DB
}
func (u *userRoleDb) GetUserRoles(tenantID string, userID int64, roleID int64) ([]*dbmodel.UserRole, error) {
var (
userRoles []*dbmodel.UserRole
err error
)
err = u.db.Model(&dbmodel.UserRole{}).Where(&dbmodel.UserRole{UserID: userID, RoleID: roleID}).Where(dbmodel.GetCommonCondition(tenantID, false)).Preload("User").Preload("Role").Find(&userRoles).Error
if err != nil {
log.Error("fail to get user-roles", zap.String("tenant_id", tenantID), zap.Int64("userID", userID), zap.Int64("roleID", roleID), zap.Error(err))
return nil, err
}
return userRoles, nil
}
func (u *userRoleDb) Insert(in *dbmodel.UserRole) error {
err := u.db.Create(in).Error
if err != nil {
log.Error("fail to insert the user-role", zap.Any("in", in), zap.Error(err))
}
return err
}
func (u *userRoleDb) Delete(tenantID string, userID int64, roleID int64) error {
err := u.db.Model(dbmodel.UserRole{}).Where(&dbmodel.UserRole{UserID: userID, RoleID: roleID}).Where(dbmodel.GetCommonCondition(tenantID, false)).Update("is_deleted", true).Error
if err != nil {
log.Error("fail to delete the user-role", zap.String("tenant_id", tenantID), zap.Int64("userID", userID), zap.Int64("roleID", roleID), zap.Error(err))
}
return err
}

View File

@ -0,0 +1,193 @@
package dao
import (
"errors"
"testing"
"github.com/milvus-io/milvus/internal/metastore/db/dbmodel"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
)
func TestUserRole_GetUserRoles(t *testing.T) {
var (
userID1 = 1
userID2 = 2
roleID1 = 10
roleID2 = 20
userRoles []*dbmodel.UserRole
getQuery func() *sqlmock.ExpectedQuery
err error
)
// mock user and role
getQuery = func() *sqlmock.ExpectedQuery {
return mock.ExpectQuery("SELECT * FROM `user_role` WHERE `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(false, tenantID)
}
getQuery().WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "user_id", "role_id"}).
AddRow(tenantID, userID1, roleID1).
AddRow(tenantID, userID2, roleID2))
mock.ExpectQuery("SELECT * FROM `role` WHERE `role`.`id` IN (?,?)").
WithArgs(roleID1, roleID2).
WillReturnRows(
sqlmock.NewRows([]string{"id", "tenant_id", "name"}).
AddRow(roleID1, tenantID, "foo1").
AddRow(roleID2, tenantID, "foo2"))
mock.ExpectQuery("SELECT * FROM `credential_users` WHERE `credential_users`.`id` IN (?,?)").
WithArgs(userID1, userID2).
WillReturnRows(
sqlmock.NewRows([]string{"id", "tenant_id", "username"}).
AddRow(userID1, tenantID, "fo1").
AddRow(userID2, tenantID, "fo2"))
userRoles, err = userRoleTestDb.GetUserRoles(tenantID, 0, 0)
assert.NoError(t, err)
assert.Equal(t, 2, len(userRoles))
assert.Equal(t, "foo1", userRoles[0].Role.Name)
assert.Equal(t, "fo1", userRoles[0].User.Username)
getQuery().WillReturnError(errors.New("test error"))
_, err = userRoleTestDb.GetUserRoles(tenantID, 0, 0)
assert.Error(t, err)
}
func TestUserRole_GetUserRolesWithUserID(t *testing.T) {
var (
userID1 = 1
roleID1 = 10
roleID2 = 20
userRoles []*dbmodel.UserRole
err error
)
mock.ExpectQuery("SELECT * FROM `user_role` WHERE `user_role`.`user_id` = ? AND `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(userID1, false, tenantID).
WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "user_id", "role_id"}).
AddRow(tenantID, userID1, roleID1).
AddRow(tenantID, userID1, roleID2))
mock.ExpectQuery("SELECT * FROM `role` WHERE `role`.`id` IN (?,?)").
WithArgs(roleID1, roleID2).
WillReturnRows(
sqlmock.NewRows([]string{"id", "tenant_id", "name"}).
AddRow(roleID1, tenantID, "foo1").
AddRow(roleID2, tenantID, "foo2"))
mock.ExpectQuery("SELECT * FROM `credential_users` WHERE `credential_users`.`id` = ?").
WithArgs(userID1).
WillReturnRows(
sqlmock.NewRows([]string{"id", "tenant_id", "username"}).
AddRow(userID1, tenantID, "fo1"))
userRoles, err = userRoleTestDb.GetUserRoles(tenantID, int64(userID1), 0)
assert.NoError(t, err)
assert.Equal(t, 2, len(userRoles))
assert.Equal(t, "foo2", userRoles[1].Role.Name)
assert.Equal(t, "fo1", userRoles[0].User.Username)
}
func TestUserRole_GetUserRolesWithRoleID(t *testing.T) {
var (
userID1 = 1
userID2 = 2
roleID1 = 10
userRoles []*dbmodel.UserRole
err error
)
mock.ExpectQuery("SELECT * FROM `user_role` WHERE `user_role`.`role_id` = ? AND `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(roleID1, false, tenantID).
WillReturnRows(
sqlmock.NewRows([]string{"tenant_id", "user_id", "role_id"}).
AddRow(tenantID, userID1, roleID1).
AddRow(tenantID, userID2, roleID1))
mock.ExpectQuery("SELECT * FROM `role` WHERE `role`.`id` = ?").
WithArgs(roleID1).
WillReturnRows(
sqlmock.NewRows([]string{"id", "tenant_id", "name"}).
AddRow(roleID1, tenantID, "foo1"))
mock.ExpectQuery("SELECT * FROM `credential_users` WHERE `credential_users`.`id` IN (?,?)").
WithArgs(userID1, userID2).
WillReturnRows(
sqlmock.NewRows([]string{"id", "tenant_id", "username"}).
AddRow(userID1, tenantID, "fo1").
AddRow(userID2, tenantID, "fo2"))
userRoles, err = userRoleTestDb.GetUserRoles(tenantID, 0, int64(roleID1))
assert.NoError(t, err)
assert.Equal(t, 2, len(userRoles))
assert.Equal(t, "foo1", userRoles[0].Role.Name)
assert.Equal(t, "fo2", userRoles[1].User.Username)
}
func TestUserRole_Insert(t *testing.T) {
var (
userRole *dbmodel.UserRole
err error
)
userRole = &dbmodel.UserRole{
Base: GetBase(),
UserID: 1,
RoleID: 1,
}
mock.ExpectBegin()
mock.ExpectExec("INSERT INTO `user_role` (`tenant_id`,`is_deleted`,`created_at`,`updated_at`,`user_id`,`role_id`) VALUES (?,?,?,?,?,?)").
WithArgs(userRole.TenantID, userRole.IsDeleted, userRole.CreatedAt, userRole.UpdatedAt, userRole.UserID, userRole.RoleID).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
err = userRoleTestDb.Insert(userRole)
assert.NoError(t, err)
}
func TestUserRole_InsertError(t *testing.T) {
var (
userRole *dbmodel.UserRole
err error
)
userRole = &dbmodel.UserRole{
Base: GetBase(),
UserID: 1,
RoleID: 1,
}
mock.ExpectBegin()
mock.ExpectExec("INSERT INTO `user_role` (`tenant_id`,`is_deleted`,`created_at`,`updated_at`,`user_id`,`role_id`) VALUES (?,?,?,?,?,?)").
WithArgs(userRole.TenantID, userRole.IsDeleted, userRole.CreatedAt, userRole.UpdatedAt, userRole.UserID, userRole.RoleID).
WillReturnError(errors.New("test error"))
mock.ExpectRollback()
err = userRoleTestDb.Insert(userRole)
assert.Error(t, err)
}
func TestUserRole_Delete(t *testing.T) {
var (
userRole *dbmodel.UserRole
getExec func() *sqlmock.ExpectedExec
err error
)
userRole = &dbmodel.UserRole{
Base: GetBase(),
UserID: 1,
RoleID: 1,
}
getExec = func() *sqlmock.ExpectedExec {
return mock.ExpectExec("UPDATE `user_role` SET `is_deleted`=?,`updated_at`=? WHERE `user_role`.`user_id` = ? AND `user_role`.`role_id` = ? AND `is_deleted` = ? AND `tenant_id` = ?").
WithArgs(true, AnyTime{}, userRole.UserID, userRole.RoleID, userRole.IsDeleted, userRole.TenantID)
}
mock.ExpectBegin()
getExec().WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
err = userRoleTestDb.Delete(userRole.TenantID, userRole.UserID, userRole.RoleID)
assert.NoError(t, err)
mock.ExpectBegin()
getExec().WillReturnError(errors.New("test error"))
mock.ExpectRollback()
err = userRoleTestDb.Delete(userRole.TenantID, userRole.UserID, userRole.RoleID)
assert.Error(t, err)
}

View File

@ -62,33 +62,42 @@ func TestUser_GetByUsername_Error(t *testing.T) {
}
func TestUser_ListUsername(t *testing.T) {
var usernames = []string{
"test_username_1",
"test_username_2",
}
var (
usernames = []string{
"test_username_1",
"test_username_2",
}
user = &dbmodel.User{
TenantID: tenantID,
EncryptedPassword: "xxx",
IsSuper: false,
}
)
// expectation
mock.ExpectQuery("SELECT `username` FROM `credential_users` WHERE tenant_id = ? AND is_deleted = false").
mock.ExpectQuery("SELECT * FROM `credential_users` WHERE tenant_id = ? AND is_deleted = false").
WithArgs(tenantID).
WillReturnRows(
sqlmock.NewRows([]string{"username"}).
AddRow(usernames[0]).
AddRow(usernames[1]))
sqlmock.NewRows([]string{"tenant_id", "username", "encrypted_password", "is_super"}).
AddRow(user.TenantID, usernames[0], user.EncryptedPassword, user.IsSuper).
AddRow(user.TenantID, usernames[1], user.EncryptedPassword, user.IsSuper))
// actual
res, err := userTestDb.ListUsername(tenantID)
res, err := userTestDb.ListUser(tenantID)
assert.Nil(t, err)
assert.Equal(t, usernames, res)
assert.Equal(t, 2, len(res))
assert.Equal(t, usernames[0], res[0].Username)
assert.Equal(t, usernames[1], res[1].Username)
}
func TestUser_ListUsername_Error(t *testing.T) {
// expectation
mock.ExpectQuery("SELECT `username` FROM `credential_users` WHERE tenant_id = ? AND is_deleted = false").
mock.ExpectQuery("SELECT * FROM `credential_users` WHERE tenant_id = ? AND is_deleted = false").
WithArgs(tenantID).
WillReturnError(errors.New("test error"))
// actual
res, err := userTestDb.ListUsername(tenantID)
res, err := userTestDb.ListUser(tenantID)
assert.Nil(t, res)
assert.Error(t, err)
}

View File

@ -0,0 +1,11 @@
package dbmodel
import "time"
type Base struct {
ID int64 `gorm:"id"`
TenantID string `gorm:"tenant_id"`
IsDeleted bool `gorm:"is_deleted"`
CreatedAt time.Time `gorm:"created_at"`
UpdatedAt time.Time `gorm:"updated_at"`
}

View File

@ -12,8 +12,18 @@ type IMetaDomain interface {
IndexDb(ctx context.Context) IIndexDb
SegmentIndexDb(ctx context.Context) ISegmentIndexDb
UserDb(ctx context.Context) IUserDb
RoleDb(ctx context.Context) IRoleDb
UserRoleDb(ctx context.Context) IUserRoleDb
GrantDb(ctx context.Context) IGrantDb
}
type ITransaction interface {
Transaction(ctx context.Context, fn func(txCtx context.Context) error) error
}
func GetCommonCondition(tenant string, isDelete bool) map[string]interface{} {
return map[string]interface{}{
"tenant_id": tenant,
"is_deleted": isDelete,
}
}

View File

@ -0,0 +1,101 @@
package dbmodel
import (
"encoding/json"
"fmt"
"github.com/milvus-io/milvus/internal/common"
)
type Grant struct {
Base
RoleID int64 `gorm:"role_id"`
Role Role `gorm:"foreignKey:RoleID"`
Object string `gorm:"object"`
ObjectName string `gorm:"object_name"`
Detail string `gorm:"detail"`
}
func (g *Grant) TableName() string {
return "grant"
}
//go:generate mockery --name=IGrantDb
type IGrantDb interface {
GetGrants(tenantID string, roleID int64, object string, objectName string) ([]*Grant, error)
Insert(in *Grant) error
Delete(tenantID string, roleID int64, object string, objectName string, privilege string) error
}
func EncodeGrantDetail(detail string, grantor string, privilege string, isAdd bool) (string, error) {
var (
grant = []string{grantor, privilege}
resBytes []byte
originGrants [][]string
index = -1
err error
handleGrant = func(grants [][]string) (string, error) {
if resBytes, err = json.Marshal(grants); err != nil {
return "", err
}
return string(resBytes), nil
}
)
if detail == "" {
if !isAdd {
return "", common.NewIgnorableError(fmt.Errorf("the empty detail can't be remove"))
}
return handleGrant(append(originGrants, grant))
}
if originGrants, err = DecodeGrantDetail(detail); err != nil {
return "", err
}
for i, origin := range originGrants {
if origin[1] == privilege {
index = i
break
}
}
if isAdd {
if index != -1 {
return detail, common.NewIgnorableError(fmt.Errorf("the grant[%s-%s] is existed", grantor, privilege))
}
return handleGrant(append(originGrants, grant))
}
if index == -1 {
return detail, common.NewIgnorableError(fmt.Errorf("the grant[%s-%s] isn't existed", grantor, privilege))
}
if len(originGrants) == 1 {
return "", nil
}
return handleGrant(append(originGrants[:index], originGrants[index+1:]...))
}
func EncodeGrantDetailForString(originDetail string, operateDetail string, isAdd bool) (string, error) {
var (
operateGrant [][]string
err error
)
if operateGrant, err = DecodeGrantDetail(operateDetail); err != nil {
return "", err
}
if len(operateGrant) != 1 || len(operateGrant[0]) != 2 {
return "", fmt.Errorf("invalid operateDetail: [%s], decode result: %+v", operateDetail, operateGrant)
}
return EncodeGrantDetail(originDetail, operateGrant[0][0], operateGrant[0][1], isAdd)
}
func DecodeGrantDetail(detail string) ([][]string, error) {
var (
grants [][]string
err error
)
if err = json.Unmarshal([]byte(detail), &grants); err != nil {
return grants, err
}
return grants, nil
}

View File

@ -0,0 +1,79 @@
// Code generated by mockery v2.14.0. DO NOT EDIT.
package mocks
import (
dbmodel "github.com/milvus-io/milvus/internal/metastore/db/dbmodel"
mock "github.com/stretchr/testify/mock"
)
// IGrantDb is an autogenerated mock type for the IGrantDb type
type IGrantDb struct {
mock.Mock
}
// Delete provides a mock function with given fields: tenantID, roleID, object, objectName, privilege
func (_m *IGrantDb) Delete(tenantID string, roleID int64, object string, objectName string, privilege string) error {
ret := _m.Called(tenantID, roleID, object, objectName, privilege)
var r0 error
if rf, ok := ret.Get(0).(func(string, int64, string, string, string) error); ok {
r0 = rf(tenantID, roleID, object, objectName, privilege)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetGrants provides a mock function with given fields: tenantID, roleID, object, objectName
func (_m *IGrantDb) GetGrants(tenantID string, roleID int64, object string, objectName string) ([]*dbmodel.Grant, error) {
ret := _m.Called(tenantID, roleID, object, objectName)
var r0 []*dbmodel.Grant
if rf, ok := ret.Get(0).(func(string, int64, string, string) []*dbmodel.Grant); ok {
r0 = rf(tenantID, roleID, object, objectName)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*dbmodel.Grant)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, int64, string, string) error); ok {
r1 = rf(tenantID, roleID, object, objectName)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Insert provides a mock function with given fields: in
func (_m *IGrantDb) Insert(in *dbmodel.Grant) error {
ret := _m.Called(in)
var r0 error
if rf, ok := ret.Get(0).(func(*dbmodel.Grant) error); ok {
r0 = rf(in)
} else {
r0 = ret.Error(0)
}
return r0
}
type mockConstructorTestingTNewIGrantDb interface {
mock.TestingT
Cleanup(func())
}
// NewIGrantDb creates a new instance of IGrantDb. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewIGrantDb(t mockConstructorTestingTNewIGrantDb) *IGrantDb {
mock := &IGrantDb{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -78,6 +78,22 @@ func (_m *IMetaDomain) FieldDb(ctx context.Context) dbmodel.IFieldDb {
return r0
}
// GrantDb provides a mock function with given fields: ctx
func (_m *IMetaDomain) GrantDb(ctx context.Context) dbmodel.IGrantDb {
ret := _m.Called(ctx)
var r0 dbmodel.IGrantDb
if rf, ok := ret.Get(0).(func(context.Context) dbmodel.IGrantDb); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(dbmodel.IGrantDb)
}
}
return r0
}
// IndexDb provides a mock function with given fields: ctx
func (_m *IMetaDomain) IndexDb(ctx context.Context) dbmodel.IIndexDb {
ret := _m.Called(ctx)
@ -110,6 +126,22 @@ func (_m *IMetaDomain) PartitionDb(ctx context.Context) dbmodel.IPartitionDb {
return r0
}
// RoleDb provides a mock function with given fields: ctx
func (_m *IMetaDomain) RoleDb(ctx context.Context) dbmodel.IRoleDb {
ret := _m.Called(ctx)
var r0 dbmodel.IRoleDb
if rf, ok := ret.Get(0).(func(context.Context) dbmodel.IRoleDb); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(dbmodel.IRoleDb)
}
}
return r0
}
// SegmentIndexDb provides a mock function with given fields: ctx
func (_m *IMetaDomain) SegmentIndexDb(ctx context.Context) dbmodel.ISegmentIndexDb {
ret := _m.Called(ctx)
@ -142,6 +174,22 @@ func (_m *IMetaDomain) UserDb(ctx context.Context) dbmodel.IUserDb {
return r0
}
// UserRoleDb provides a mock function with given fields: ctx
func (_m *IMetaDomain) UserRoleDb(ctx context.Context) dbmodel.IUserRoleDb {
ret := _m.Called(ctx)
var r0 dbmodel.IUserRoleDb
if rf, ok := ret.Get(0).(func(context.Context) dbmodel.IUserRoleDb); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(dbmodel.IUserRoleDb)
}
}
return r0
}
type mockConstructorTestingTNewIMetaDomain interface {
mock.TestingT
Cleanup(func())

View File

@ -0,0 +1,79 @@
// Code generated by mockery v2.14.0. DO NOT EDIT.
package mocks
import (
dbmodel "github.com/milvus-io/milvus/internal/metastore/db/dbmodel"
mock "github.com/stretchr/testify/mock"
)
// IRoleDb is an autogenerated mock type for the IRoleDb type
type IRoleDb struct {
mock.Mock
}
// Delete provides a mock function with given fields: tenantID, name
func (_m *IRoleDb) Delete(tenantID string, name string) error {
ret := _m.Called(tenantID, name)
var r0 error
if rf, ok := ret.Get(0).(func(string, string) error); ok {
r0 = rf(tenantID, name)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetRoles provides a mock function with given fields: tenantID, name
func (_m *IRoleDb) GetRoles(tenantID string, name string) ([]*dbmodel.Role, error) {
ret := _m.Called(tenantID, name)
var r0 []*dbmodel.Role
if rf, ok := ret.Get(0).(func(string, string) []*dbmodel.Role); ok {
r0 = rf(tenantID, name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*dbmodel.Role)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(tenantID, name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Insert provides a mock function with given fields: in
func (_m *IRoleDb) Insert(in *dbmodel.Role) error {
ret := _m.Called(in)
var r0 error
if rf, ok := ret.Get(0).(func(*dbmodel.Role) error); ok {
r0 = rf(in)
} else {
r0 = ret.Error(0)
}
return r0
}
type mockConstructorTestingTNewIRoleDb interface {
mock.TestingT
Cleanup(func())
}
// NewIRoleDb creates a new instance of IRoleDb. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewIRoleDb(t mockConstructorTestingTNewIRoleDb) *IRoleDb {
mock := &IRoleDb{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -49,16 +49,16 @@ func (_m *IUserDb) Insert(in *dbmodel.User) error {
return r0
}
// ListUsername provides a mock function with given fields: tenantID
func (_m *IUserDb) ListUsername(tenantID string) ([]string, error) {
// ListUser provides a mock function with given fields: tenantID
func (_m *IUserDb) ListUser(tenantID string) ([]*dbmodel.User, error) {
ret := _m.Called(tenantID)
var r0 []string
if rf, ok := ret.Get(0).(func(string) []string); ok {
var r0 []*dbmodel.User
if rf, ok := ret.Get(0).(func(string) []*dbmodel.User); ok {
r0 = rf(tenantID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
r0 = ret.Get(0).([]*dbmodel.User)
}
}

View File

@ -0,0 +1,79 @@
// Code generated by mockery v2.14.0. DO NOT EDIT.
package mocks
import (
dbmodel "github.com/milvus-io/milvus/internal/metastore/db/dbmodel"
mock "github.com/stretchr/testify/mock"
)
// IUserRoleDb is an autogenerated mock type for the IUserRoleDb type
type IUserRoleDb struct {
mock.Mock
}
// Delete provides a mock function with given fields: tenantID, userID, roleID
func (_m *IUserRoleDb) Delete(tenantID string, userID int64, roleID int64) error {
ret := _m.Called(tenantID, userID, roleID)
var r0 error
if rf, ok := ret.Get(0).(func(string, int64, int64) error); ok {
r0 = rf(tenantID, userID, roleID)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetUserRoles provides a mock function with given fields: tenantID, userID, roleID
func (_m *IUserRoleDb) GetUserRoles(tenantID string, userID int64, roleID int64) ([]*dbmodel.UserRole, error) {
ret := _m.Called(tenantID, userID, roleID)
var r0 []*dbmodel.UserRole
if rf, ok := ret.Get(0).(func(string, int64, int64) []*dbmodel.UserRole); ok {
r0 = rf(tenantID, userID, roleID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*dbmodel.UserRole)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, int64, int64) error); ok {
r1 = rf(tenantID, userID, roleID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Insert provides a mock function with given fields: in
func (_m *IUserRoleDb) Insert(in *dbmodel.UserRole) error {
ret := _m.Called(in)
var r0 error
if rf, ok := ret.Get(0).(func(*dbmodel.UserRole) error); ok {
r0 = rf(in)
} else {
r0 = ret.Error(0)
}
return r0
}
type mockConstructorTestingTNewIUserRoleDb interface {
mock.TestingT
Cleanup(func())
}
// NewIUserRoleDb creates a new instance of IUserRoleDb. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewIUserRoleDb(t mockConstructorTestingTNewIUserRoleDb) *IUserRoleDb {
mock := &IUserRoleDb{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,23 @@
package dbmodel
import "github.com/milvus-io/milvus/internal/proto/milvuspb"
type Role struct {
Base
Name string `gorm:"name"`
}
func (r *Role) TableName() string {
return "role"
}
func (r *Role) Unmarshal() *milvuspb.RoleEntity {
return &milvuspb.RoleEntity{Name: r.Name}
}
//go:generate mockery --name=IRoleDb
type IRoleDb interface {
GetRoles(tenantID string, name string) ([]*Role, error)
Insert(in *Role) error
Delete(tenantID string, name string) error
}

View File

@ -24,7 +24,7 @@ func (v User) TableName() string {
//go:generate mockery --name=IUserDb
type IUserDb interface {
GetByUsername(tenantID string, username string) (*User, error)
ListUsername(tenantID string) ([]string, error)
ListUser(tenantID string) ([]*User, error)
Insert(in *User) error
MarkDeletedByUsername(tenantID string, username string) error
}

View File

@ -0,0 +1,21 @@
package dbmodel
type UserRole struct {
Base
UserID int64 `gorm:"user_id"`
RoleID int64 `gorm:"role_id"`
User User `gorm:"foreignKey:UserID"`
Role Role `gorm:"foreignKey:RoleID"`
}
func (u *UserRole) TableName() string {
return "user_role"
}
//go:generate mockery --name=IUserRoleDb
type IUserRoleDb interface {
GetUserRoles(tenantID string, userID int64, roleID int64) ([]*UserRole, error)
Insert(in *UserRole) error
Delete(tenantID string, userID int64, roleID int64) error
}

View File

@ -8,6 +8,10 @@ import (
"reflect"
"runtime"
"github.com/milvus-io/milvus/internal/common"
"github.com/milvus-io/milvus/internal/util"
"github.com/milvus-io/milvus/internal/log"
"github.com/milvus-io/milvus/internal/metastore"
"github.com/milvus-io/milvus/internal/metastore/db/dbmodel"
@ -763,57 +767,292 @@ func (tc *Catalog) DropCredential(ctx context.Context, username string) error {
func (tc *Catalog) ListCredentials(ctx context.Context) ([]string, error) {
tenantID := contextutil.TenantID(ctx)
usernames, err := tc.metaDomain.UserDb(ctx).ListUsername(tenantID)
users, err := tc.metaDomain.UserDb(ctx).ListUser(tenantID)
if err != nil {
return nil, err
}
var usernames []string
for _, user := range users {
usernames = append(usernames, user.Username)
}
return usernames, nil
}
func (tc *Catalog) CreateRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity) error {
//TODO implement me
return nil
var err error
if _, err = tc.GetRoleIDByName(ctx, tenant, entity.Name); err != nil && !common.IsKeyNotExistError(err) {
return err
}
if err == nil {
return common.NewIgnorableError(fmt.Errorf("the role[%s] has existed", entity.Name))
}
return tc.metaDomain.RoleDb(ctx).Insert(&dbmodel.Role{
Base: dbmodel.Base{TenantID: tenant},
Name: entity.Name,
})
}
func (tc *Catalog) DropRole(ctx context.Context, tenant string, roleName string) error {
//TODO implement me
return nil
return tc.metaDomain.RoleDb(ctx).Delete(tenant, roleName)
}
func (tc *Catalog) OperateUserRole(ctx context.Context, tenant string, userEntity *milvuspb.UserEntity, roleEntity *milvuspb.RoleEntity, operateType milvuspb.OperateUserRoleType) error {
//TODO implement me
return nil
func (tc *Catalog) GetRoleIDByName(ctx context.Context, tenant string, name string) (int64, error) {
var (
roles []*dbmodel.Role
err error
)
if roles, err = tc.metaDomain.RoleDb(ctx).GetRoles(tenant, name); err != nil {
return 0, err
}
if len(roles) < 1 {
return 0, common.NewKeyNotExistError(fmt.Sprintf("%s/%s", tenant, name))
}
return roles[0].ID, nil
}
func (tc *Catalog) SelectRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) {
//TODO implement me
return nil, nil
func (tc *Catalog) AlterUserRole(ctx context.Context, tenant string, userEntity *milvuspb.UserEntity, roleEntity *milvuspb.RoleEntity, operateType milvuspb.OperateUserRoleType) error {
var (
user *dbmodel.User
roleID int64
userRole *dbmodel.UserRole
userRoles []*dbmodel.UserRole
err error
)
if user, err = tc.metaDomain.UserDb(ctx).GetByUsername(tenant, userEntity.Name); err != nil {
log.Error("fail to get userID by the username", zap.String("username", userEntity.Name), zap.Error(err))
return err
}
if roleID, err = tc.GetRoleIDByName(ctx, tenant, roleEntity.Name); err != nil {
log.Error("fail to get roleID by the role name", zap.String("role_name", roleEntity.Name), zap.Error(err))
return err
}
userRole = &dbmodel.UserRole{Base: dbmodel.Base{TenantID: tenant}, UserID: user.ID, RoleID: roleID}
userRoles, err = tc.metaDomain.UserRoleDb(ctx).GetUserRoles(userRole.TenantID, userRole.UserID, userRole.RoleID)
if err != nil {
return err
}
switch operateType {
case milvuspb.OperateUserRoleType_AddUserToRole:
if len(userRoles) > 0 {
return common.NewIgnorableError(fmt.Errorf("the user-role[%s-%s] is existed", userEntity.Name, roleEntity.Name))
}
return tc.metaDomain.UserRoleDb(ctx).Insert(userRole)
case milvuspb.OperateUserRoleType_RemoveUserFromRole:
if len(userRoles) < 1 {
return common.NewIgnorableError(fmt.Errorf("the user-role[%s-%s] isn't existed", userEntity.Name, roleEntity.Name))
}
return tc.metaDomain.UserRoleDb(ctx).Delete(userRole.TenantID, userRole.UserID, userRole.RoleID)
default:
err = fmt.Errorf("invalid operate type: %d", operateType)
log.Error("error: ", zap.Error(err))
return err
}
}
func (tc *Catalog) SelectUser(ctx context.Context, tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error) {
//TODO implement me
return nil, nil
func (tc *Catalog) ListRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) {
var (
roleName string
roles []*dbmodel.Role
results []*milvuspb.RoleResult
err error
)
if entity != nil {
roleName = entity.Name
}
roles, err = tc.metaDomain.RoleDb(ctx).GetRoles(tenant, roleName)
if err != nil {
return nil, err
}
for _, role := range roles {
var users []*milvuspb.UserEntity
var userRoles []*dbmodel.UserRole
if includeUserInfo {
if userRoles, err = tc.metaDomain.UserRoleDb(ctx).GetUserRoles(tenant, 0, role.ID); err != nil {
return nil, err
}
for _, userRole := range userRoles {
users = append(users, &milvuspb.UserEntity{Name: userRole.User.Username})
}
}
results = append(results, &milvuspb.RoleResult{
Role: role.Unmarshal(),
Users: users,
})
}
if !funcutil.IsEmptyString(roleName) && len(results) == 0 {
return nil, common.NewKeyNotExistError(fmt.Sprintf("%s/%s", tenant, roleName))
}
return results, nil
}
func (tc *Catalog) OperatePrivilege(ctx context.Context, tenant string, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error {
//TODO implement me
return nil
func (tc *Catalog) ListUser(ctx context.Context, tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error) {
var (
users []*dbmodel.User
results []*milvuspb.UserResult
username string
err error
)
if entity != nil {
var user *dbmodel.User
username = entity.Name
if user, err = tc.metaDomain.UserDb(ctx).GetByUsername(tenant, username); err != nil {
return nil, err
}
users = append(users, user)
} else {
if users, err = tc.metaDomain.UserDb(ctx).ListUser(tenant); err != nil {
return nil, err
}
}
for _, user := range users {
var roles []*milvuspb.RoleEntity
var userRoles []*dbmodel.UserRole
if includeRoleInfo {
if userRoles, err = tc.metaDomain.UserRoleDb(ctx).GetUserRoles(tenant, user.ID, 0); err != nil {
return nil, err
}
for _, userRole := range userRoles {
roles = append(roles, &milvuspb.RoleEntity{Name: userRole.Role.Name})
}
}
results = append(results, &milvuspb.UserResult{
User: &milvuspb.UserEntity{Name: user.Username},
Roles: roles,
})
}
return results, nil
}
func (tc *Catalog) SelectGrant(ctx context.Context, tenant string, entity *milvuspb.GrantEntity) ([]*milvuspb.GrantEntity, error) {
//TODO implement me
return nil, nil
func (tc *Catalog) AlterGrant(ctx context.Context, tenant string, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error {
var (
roleID int64
detail string
err error
)
if roleID, err = tc.GetRoleIDByName(ctx, tenant, entity.Role.Name); err != nil {
return err
}
switch operateType {
case milvuspb.OperatePrivilegeType_Revoke:
return tc.metaDomain.GrantDb(ctx).
Delete(tenant, roleID, entity.Object.Name, entity.ObjectName, entity.Grantor.Privilege.Name)
case milvuspb.OperatePrivilegeType_Grant:
if detail, err = dbmodel.EncodeGrantDetail("", entity.Grantor.User.Name, entity.Grantor.Privilege.Name, true); err != nil {
log.Error("fail to encode grant detail", zap.String("tenant", tenant), zap.Any("entity", entity), zap.Error(err))
return err
}
return tc.metaDomain.GrantDb(ctx).Insert(&dbmodel.Grant{
Base: dbmodel.Base{TenantID: tenant},
RoleID: roleID,
Object: entity.Object.Name,
ObjectName: entity.ObjectName,
Detail: detail,
})
default:
err = fmt.Errorf("invalid operate type: %d", operateType)
log.Error("error: ", zap.Error(err))
return err
}
}
func (tc *Catalog) ListGrant(ctx context.Context, tenant string, entity *milvuspb.GrantEntity) ([]*milvuspb.GrantEntity, error) {
var (
roleID int64
object string
objectName string
grants []*dbmodel.Grant
grantEntities []*milvuspb.GrantEntity
details [][]string
privilegeName string
err error
)
if !funcutil.IsEmptyString(entity.ObjectName) && entity.Object != nil && !funcutil.IsEmptyString(entity.Object.Name) {
object = entity.Object.Name
objectName = entity.ObjectName
}
if roleID, err = tc.GetRoleIDByName(ctx, tenant, entity.Role.Name); err != nil {
log.Error("fail to get roleID by the role name", zap.String("role_name", entity.Role.Name), zap.Error(err))
return nil, err
}
if grants, err = tc.metaDomain.GrantDb(ctx).GetGrants(tenant, roleID, object, objectName); err != nil {
return nil, err
}
for _, grant := range grants {
if details, err = dbmodel.DecodeGrantDetail(grant.Detail); err != nil {
log.Error("fail to decode grant detail", zap.Any("detail", grant.Detail), zap.Error(err))
return nil, err
}
for _, detail := range details {
if len(detail) != 2 {
log.Error("invalid operateDetail", zap.Any("detail", detail))
return nil, fmt.Errorf("invalid operateDetail: [%s], decode result: %+v", grant.Detail, details)
}
privilegeName = util.PrivilegeNameForAPI(detail[1])
if detail[1] == util.AnyWord {
privilegeName = util.AnyWord
}
grantEntities = append(grantEntities, &milvuspb.GrantEntity{
Role: &milvuspb.RoleEntity{Name: grant.Role.Name},
Object: &milvuspb.ObjectEntity{Name: grant.Object},
ObjectName: grant.ObjectName,
Grantor: &milvuspb.GrantorEntity{
User: &milvuspb.UserEntity{Name: detail[0]},
Privilege: &milvuspb.PrivilegeEntity{Name: privilegeName},
},
})
}
}
if !funcutil.IsEmptyString(object) && !funcutil.IsEmptyString(objectName) && len(grantEntities) == 0 {
return nil, common.NewKeyNotExistError(fmt.Sprintf("%s/%s/%s/%s", tenant, entity.Role.Name, object, objectName))
}
return grantEntities, nil
}
func (tc *Catalog) ListPolicy(ctx context.Context, tenant string) ([]string, error) {
//TODO implement me
return nil, nil
var (
grants []*dbmodel.Grant
details [][]string
policies []string
err error
)
if grants, err = tc.metaDomain.GrantDb(ctx).GetGrants(tenant, 0, "", ""); err != nil {
return nil, err
}
for _, grant := range grants {
if details, err = dbmodel.DecodeGrantDetail(grant.Detail); err != nil {
log.Error("fail to decode grant detail", zap.Any("detail", grant.Detail), zap.Error(err))
return nil, err
}
for _, detail := range details {
if len(detail) != 2 {
log.Error("invalid operateDetail", zap.String("tenant", tenant), zap.Strings("detail", detail))
return nil, fmt.Errorf("invalid operateDetail: %+v", detail)
}
policies = append(policies,
funcutil.PolicyForPrivilege(grant.Role.Name, grant.Object, grant.ObjectName, detail[1]))
}
}
return policies, nil
}
func (tc *Catalog) ListUserRole(ctx context.Context, tenant string) ([]string, error) {
//TODO implement me
return nil, nil
var (
userRoleStrs []string
userRoles []*dbmodel.UserRole
err error
)
if userRoles, err = tc.metaDomain.UserRoleDb(ctx).GetUserRoles(tenant, 0, 0); err != nil {
return nil, err
}
for _, userRole := range userRoles {
userRoleStrs = append(userRoleStrs, funcutil.EncodeUserRoleCache(userRole.User.Username, userRole.Role.Name))
}
return userRoleStrs, nil
}
func (tc *Catalog) Close() {

View File

@ -8,12 +8,15 @@ import (
"testing"
"time"
"github.com/milvus-io/milvus/internal/common"
"github.com/milvus-io/milvus/internal/proto/milvuspb"
"github.com/milvus-io/milvus/internal/metastore"
"github.com/milvus-io/milvus/internal/metastore/db/dbmodel"
"github.com/milvus-io/milvus/internal/metastore/db/dbmodel/mocks"
"github.com/milvus-io/milvus/internal/metastore/model"
"github.com/milvus-io/milvus/internal/proto/commonpb"
"github.com/milvus-io/milvus/internal/proto/milvuspb"
"github.com/milvus-io/milvus/internal/proto/schemapb"
"github.com/milvus-io/milvus/internal/util/contextutil"
"github.com/milvus-io/milvus/internal/util/funcutil"
@ -52,6 +55,9 @@ var (
aliasDbMock *mocks.ICollAliasDb
segIndexDbMock *mocks.ISegmentIndexDb
userDbMock *mocks.IUserDb
roleDbMock *mocks.IRoleDb
userRoleDbMock *mocks.IUserRoleDb
grantDbMock *mocks.IGrantDb
mockCatalog *Catalog
)
@ -68,6 +74,9 @@ func TestMain(m *testing.M) {
aliasDbMock = &mocks.ICollAliasDb{}
segIndexDbMock = &mocks.ISegmentIndexDb{}
userDbMock = &mocks.IUserDb{}
roleDbMock = &mocks.IRoleDb{}
userRoleDbMock = &mocks.IUserRoleDb{}
grantDbMock = &mocks.IGrantDb{}
metaDomainMock = &mocks.IMetaDomain{}
metaDomainMock.On("CollectionDb", ctx).Return(collDbMock)
@ -78,6 +87,9 @@ func TestMain(m *testing.M) {
metaDomainMock.On("CollAliasDb", ctx).Return(aliasDbMock)
metaDomainMock.On("SegmentIndexDb", ctx).Return(segIndexDbMock)
metaDomainMock.On("UserDb", ctx).Return(userDbMock)
metaDomainMock.On("RoleDb", ctx).Return(roleDbMock)
metaDomainMock.On("UserRoleDb", ctx).Return(userRoleDbMock)
metaDomainMock.On("GrantDb", ctx).Return(grantDbMock)
mockCatalog = mockMetaCatalog(metaDomainMock)
@ -1348,21 +1360,24 @@ func TestTableCatalog_DropCredential_MarkUserDeletedError(t *testing.T) {
}
func TestTableCatalog_ListCredentials(t *testing.T) {
usernames := []string{username}
user := &dbmodel.User{
Username: username,
EncryptedPassword: password,
}
// expectation
userDbMock.On("ListUsername", tenantID).Return(usernames, nil).Once()
userDbMock.On("ListUser", tenantID).Return([]*dbmodel.User{user}, nil).Once()
// actual
res, gotErr := mockCatalog.ListCredentials(ctx)
require.NoError(t, gotErr)
require.Equal(t, usernames, res)
require.Equal(t, []string{username}, res)
}
func TestTableCatalog_ListCredentials_SelectUsernamesError(t *testing.T) {
// expectation
errTest := errors.New("test error")
userDbMock.On("ListUsername", tenantID).Return(nil, errTest).Once()
userDbMock.On("ListUser", tenantID).Return(nil, errTest).Once()
// actual
res, gotErr := mockCatalog.ListCredentials(ctx)
@ -1371,55 +1386,715 @@ func TestTableCatalog_ListCredentials_SelectUsernamesError(t *testing.T) {
}
func TestTableCatalog_CreateRole(t *testing.T) {
//TODO implement me
gotErr := mockCatalog.CreateRole(ctx, tenantID, nil)
require.Nil(t, gotErr)
var (
roleName = "foo"
err error
)
roleDbMock.On("GetRoles", tenantID, roleName).Return([]*dbmodel.Role{}, nil).Once()
roleDbMock.On("Insert", mock.Anything).Return(nil).Once()
err = mockCatalog.CreateRole(ctx, tenantID, &milvuspb.RoleEntity{Name: roleName})
require.NoError(t, err)
}
func TestTableCatalog_CreateRole_Error(t *testing.T) {
var (
roleName = "foo"
err error
)
roleDbMock.On("GetRoles", tenantID, roleName).Return(nil, errors.New("test error")).Once()
err = mockCatalog.CreateRole(ctx, tenantID, &milvuspb.RoleEntity{Name: roleName})
require.Error(t, err)
roleDbMock.On("GetRoles", tenantID, roleName).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: 1}}}, nil).Once()
err = mockCatalog.CreateRole(ctx, tenantID, &milvuspb.RoleEntity{Name: roleName})
require.Equal(t, true, common.IsIgnorableError(err))
roleDbMock.On("GetRoles", tenantID, roleName).Return([]*dbmodel.Role{}, nil).Once()
roleDbMock.On("Insert", mock.Anything).Return(errors.New("test error")).Once()
err = mockCatalog.CreateRole(ctx, tenantID, &milvuspb.RoleEntity{Name: roleName})
require.Error(t, err)
}
func TestTableCatalog_DropRole(t *testing.T) {
//TODO implement me
gotErr := mockCatalog.DropRole(ctx, tenantID, "")
require.Nil(t, gotErr)
var (
roleName = "foo"
err error
)
roleDbMock.On("Delete", tenantID, roleName).Return(nil).Once()
err = mockCatalog.DropRole(ctx, tenantID, roleName)
require.NoError(t, err)
}
func TestTableCatalog_OperateUserRole(t *testing.T) {
//TODO implement me
gotErr := mockCatalog.OperateUserRole(ctx, tenantID, nil, nil, milvuspb.OperateUserRoleType_AddUserToRole)
require.Nil(t, gotErr)
func TestTableCatalog_DropRole_Error(t *testing.T) {
var (
roleName = "foo"
err error
)
roleDbMock.On("Delete", tenantID, roleName).Return(errors.New("test error")).Once()
err = mockCatalog.DropRole(ctx, tenantID, roleName)
require.Error(t, err)
}
func TestTableCatalog_SelectRole(t *testing.T) {
//TODO implement me
_, gotErr := mockCatalog.SelectRole(ctx, tenantID, nil, false)
require.Nil(t, gotErr)
func TestTableCatalog_AlterUserRole(t *testing.T) {
var (
username = "foo"
userID = 100
roleName = "fo"
roleID = 10
err error
)
userDbMock.On("GetByUsername", tenantID, username).Return(&dbmodel.User{ID: int64(userID)}, nil).Once()
roleDbMock.On("GetRoles", tenantID, roleName).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: int64(roleID)}}}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(userID), int64(roleID)).Return([]*dbmodel.UserRole{}, nil).Once()
userRoleDbMock.On("Insert", mock.Anything).Return(nil).Once()
err = mockCatalog.AlterUserRole(ctx, tenantID, &milvuspb.UserEntity{Name: username}, &milvuspb.RoleEntity{Name: roleName}, milvuspb.OperateUserRoleType_AddUserToRole)
require.NoError(t, err)
}
func TestTableCatalog_SelectUser(t *testing.T) {
//TODO implement me
_, gotErr := mockCatalog.SelectUser(ctx, tenantID, nil, false)
require.Nil(t, gotErr)
func TestTableCatalog_AlterUserRole_GetUserIDError(t *testing.T) {
var (
username = "foo"
roleName = "fo"
err error
)
userDbMock.On("GetByUsername", tenantID, username).Return(nil, errors.New("test error")).Once()
err = mockCatalog.AlterUserRole(ctx, tenantID, &milvuspb.UserEntity{Name: username}, &milvuspb.RoleEntity{Name: roleName}, milvuspb.OperateUserRoleType_AddUserToRole)
require.Error(t, err)
}
func TestTableCatalog_OperatePrivilege(t *testing.T) {
//TODO implement me
gotErr := mockCatalog.OperatePrivilege(ctx, tenantID, nil, milvuspb.OperatePrivilegeType_Revoke)
require.Nil(t, gotErr)
func TestTableCatalog_AlterUserRole_GetRoleIDError(t *testing.T) {
var (
username = "foo"
userID = 100
roleName = "fo"
err error
)
userDbMock.On("GetByUsername", tenantID, username).Return(&dbmodel.User{ID: int64(userID)}, nil).Once()
roleDbMock.On("GetRoles", tenantID, roleName).Return(nil, errors.New("test error")).Once()
err = mockCatalog.AlterUserRole(ctx, tenantID, &milvuspb.UserEntity{Name: username}, &milvuspb.RoleEntity{Name: roleName}, milvuspb.OperateUserRoleType_AddUserToRole)
require.Error(t, err)
}
func TestTableCatalog_SelectGrant(t *testing.T) {
//TODO implement me
_, gotErr := mockCatalog.SelectGrant(ctx, tenantID, nil)
require.Nil(t, gotErr)
func TestTableCatalog_AlterUserRole_GetUserRoleError(t *testing.T) {
var (
username = "foo"
userID = 100
roleName = "fo"
roleID = 10
err error
)
userDbMock.On("GetByUsername", tenantID, username).Return(&dbmodel.User{ID: int64(userID)}, nil).Once()
roleDbMock.On("GetRoles", tenantID, roleName).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: int64(roleID)}}}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(userID), int64(roleID)).Return([]*dbmodel.UserRole{}, errors.New("test error")).Once()
err = mockCatalog.AlterUserRole(ctx, tenantID, &milvuspb.UserEntity{Name: username}, &milvuspb.RoleEntity{Name: roleName}, milvuspb.OperateUserRoleType_AddUserToRole)
require.Error(t, err)
}
func TestTableCatalog_AlterUserRole_RepeatUserRole(t *testing.T) {
var (
username = "foo"
userID = 100
roleName = "fo"
roleID = 10
err error
)
userDbMock.On("GetByUsername", tenantID, username).Return(&dbmodel.User{ID: int64(userID)}, nil).Once()
roleDbMock.On("GetRoles", tenantID, roleName).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: int64(roleID)}}}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(userID), int64(roleID)).Return([]*dbmodel.UserRole{{}}, nil).Once()
err = mockCatalog.AlterUserRole(ctx, tenantID, &milvuspb.UserEntity{Name: username}, &milvuspb.RoleEntity{Name: roleName}, milvuspb.OperateUserRoleType_AddUserToRole)
require.Error(t, err)
require.Equal(t, true, common.IsIgnorableError(err))
}
func TestTableCatalog_AlterUserRole_InsertError(t *testing.T) {
var (
username = "foo"
userID = 100
roleName = "fo"
roleID = 10
err error
)
userDbMock.On("GetByUsername", tenantID, username).Return(&dbmodel.User{ID: int64(userID)}, nil).Once()
roleDbMock.On("GetRoles", tenantID, roleName).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: int64(roleID)}}}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(userID), int64(roleID)).Return([]*dbmodel.UserRole{}, nil).Once()
userRoleDbMock.On("Insert", mock.Anything).Return(errors.New("test error")).Once()
err = mockCatalog.AlterUserRole(ctx, tenantID, &milvuspb.UserEntity{Name: username}, &milvuspb.RoleEntity{Name: roleName}, milvuspb.OperateUserRoleType_AddUserToRole)
require.Error(t, err)
}
func TestTableCatalog_AlterUserRole_Delete(t *testing.T) {
var (
username = "foo"
userID = 100
roleName = "fo"
roleID = 10
err error
)
userDbMock.On("GetByUsername", tenantID, username).Return(&dbmodel.User{ID: int64(userID)}, nil).Once()
roleDbMock.On("GetRoles", tenantID, roleName).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: int64(roleID)}}}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(userID), int64(roleID)).Return([]*dbmodel.UserRole{{}}, nil).Once()
userRoleDbMock.On("Delete", tenantID, int64(userID), int64(roleID)).Return(nil).Once()
err = mockCatalog.AlterUserRole(ctx, tenantID, &milvuspb.UserEntity{Name: username}, &milvuspb.RoleEntity{Name: roleName}, milvuspb.OperateUserRoleType_RemoveUserFromRole)
require.NoError(t, err)
}
func TestTableCatalog_AlterUserRole_DeleteError(t *testing.T) {
var (
username = "foo"
userID = 100
roleName = "fo"
roleID = 10
err error
)
userDbMock.On("GetByUsername", tenantID, username).Return(&dbmodel.User{ID: int64(userID)}, nil).Once()
roleDbMock.On("GetRoles", tenantID, roleName).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: int64(roleID)}}}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(userID), int64(roleID)).Return([]*dbmodel.UserRole{}, nil).Once()
err = mockCatalog.AlterUserRole(ctx, tenantID, &milvuspb.UserEntity{Name: username}, &milvuspb.RoleEntity{Name: roleName}, milvuspb.OperateUserRoleType_RemoveUserFromRole)
require.Error(t, err)
require.True(t, common.IsIgnorableError(err))
userDbMock.On("GetByUsername", tenantID, username).Return(&dbmodel.User{ID: int64(userID)}, nil).Once()
roleDbMock.On("GetRoles", tenantID, roleName).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: int64(roleID)}}}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(userID), int64(roleID)).Return([]*dbmodel.UserRole{{}}, nil).Once()
userRoleDbMock.On("Delete", tenantID, int64(userID), int64(roleID)).Return(errors.New("test error")).Once()
err = mockCatalog.AlterUserRole(ctx, tenantID, &milvuspb.UserEntity{Name: username}, &milvuspb.RoleEntity{Name: roleName}, milvuspb.OperateUserRoleType_RemoveUserFromRole)
require.Error(t, err)
}
func TestTableCatalog_AlterUserRole_InvalidType(t *testing.T) {
var (
username = "foo"
userID = 100
roleName = "fo"
roleID = 10
err error
)
userDbMock.On("GetByUsername", tenantID, username).Return(&dbmodel.User{ID: int64(userID)}, nil).Once()
roleDbMock.On("GetRoles", tenantID, roleName).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: int64(roleID)}}}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(userID), int64(roleID)).Return([]*dbmodel.UserRole{{}}, nil).Once()
err = mockCatalog.AlterUserRole(ctx, tenantID, &milvuspb.UserEntity{Name: username}, &milvuspb.RoleEntity{Name: roleName}, 100)
require.Error(t, err)
}
func TestTableCatalog_ListRole_AllRole(t *testing.T) {
var (
roleName1 = "foo1"
roleName2 = "foo2"
result []*milvuspb.RoleResult
err error
)
roleDbMock.On("GetRoles", tenantID, "").Return([]*dbmodel.Role{{Name: roleName1}, {Name: roleName2}}, nil).Once()
result, err = mockCatalog.ListRole(ctx, tenantID, nil, false)
require.NoError(t, err)
require.Equal(t, 2, len(result))
require.Equal(t, roleName1, result[0].Role.Name)
}
func TestTableCatalog_ListRole_AllRole_IncludeUserInfo(t *testing.T) {
var (
roleName1 = "foo1"
username1 = "fo1"
username2 = "fo2"
roleName2 = "foo2"
result []*milvuspb.RoleResult
err error
)
roleDbMock.On("GetRoles", tenantID, "").Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: 1}, Name: roleName1}, {Base: dbmodel.Base{ID: 10}, Name: roleName2}}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(0), int64(1)).Return([]*dbmodel.UserRole{{User: dbmodel.User{Username: username1}}, {User: dbmodel.User{Username: username2}}}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(0), int64(10)).Return([]*dbmodel.UserRole{}, nil).Once()
result, err = mockCatalog.ListRole(ctx, tenantID, nil, true)
require.NoError(t, err)
require.Equal(t, 2, len(result))
require.Equal(t, roleName1, result[0].Role.Name)
require.Equal(t, 2, len(result[0].Users))
require.Equal(t, username1, result[0].Users[0].Name)
require.Equal(t, username2, result[0].Users[1].Name)
require.Equal(t, roleName2, result[1].Role.Name)
}
func TestTableCatalog_ListRole_AllRole_Empty(t *testing.T) {
var (
result []*milvuspb.RoleResult
err error
)
roleDbMock.On("GetRoles", tenantID, "").Return([]*dbmodel.Role{}, nil).Once()
result, err = mockCatalog.ListRole(ctx, tenantID, nil, false)
require.NoError(t, err)
require.Equal(t, 0, len(result))
}
func TestTableCatalog_ListRole_OneRole(t *testing.T) {
var (
roleName1 = "foo1"
result []*milvuspb.RoleResult
err error
)
roleDbMock.On("GetRoles", tenantID, roleName1).Return([]*dbmodel.Role{{Name: roleName1}}, nil).Once()
result, err = mockCatalog.ListRole(ctx, tenantID, &milvuspb.RoleEntity{Name: roleName1}, false)
require.NoError(t, err)
require.Equal(t, 1, len(result))
require.Equal(t, roleName1, result[0].Role.Name)
}
func TestTableCatalog_ListRole_OneRole_IncludeUserInfo(t *testing.T) {
var (
roleName1 = "foo1"
username1 = "fo1"
username2 = "fo2"
result []*milvuspb.RoleResult
err error
)
roleDbMock.On("GetRoles", tenantID, roleName1).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: 1}, Name: roleName1}}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(0), int64(1)).Return([]*dbmodel.UserRole{{User: dbmodel.User{Username: username1}}, {User: dbmodel.User{Username: username2}}}, nil).Once()
result, err = mockCatalog.ListRole(ctx, tenantID, &milvuspb.RoleEntity{Name: roleName1}, true)
require.NoError(t, err)
require.Equal(t, 1, len(result))
require.Equal(t, roleName1, result[0].Role.Name)
require.Equal(t, 2, len(result[0].Users))
require.Equal(t, username1, result[0].Users[0].Name)
require.Equal(t, username2, result[0].Users[1].Name)
}
func TestTableCatalog_ListRole_OneRole_Empty(t *testing.T) {
var (
roleName = "foo"
err error
)
roleDbMock.On("GetRoles", tenantID, roleName).Return([]*dbmodel.Role{}, nil).Once()
_, err = mockCatalog.ListRole(ctx, tenantID, &milvuspb.RoleEntity{Name: roleName}, false)
require.Error(t, err)
}
func TestTableCatalog_ListRole_GetRolesError(t *testing.T) {
roleDbMock.On("GetRoles", tenantID, "").Return(nil, errors.New("test error")).Once()
_, err := mockCatalog.ListRole(ctx, tenantID, nil, false)
require.Error(t, err)
}
func TestTableCatalog_ListRole_GetUserRolesError(t *testing.T) {
roleDbMock.On("GetRoles", tenantID, "").Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: 1}, Name: "foo"}}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(0), int64(1)).Return(nil, errors.New("test error")).Once()
_, err := mockCatalog.ListRole(ctx, tenantID, nil, true)
require.Error(t, err)
}
func TestTableCatalog_ListUser_AllUser(t *testing.T) {
var (
username1 = "foo1"
username2 = "foo2"
result []*milvuspb.UserResult
err error
)
userDbMock.On("ListUser", tenantID).Return([]*dbmodel.User{{Username: username1}, {Username: username2}}, nil).Once()
result, err = mockCatalog.ListUser(ctx, tenantID, nil, false)
require.NoError(t, err)
require.Equal(t, 2, len(result))
require.Equal(t, username1, result[0].User.Name)
}
func TestTableCatalog_ListUser_AllUser_IncludeRoleInfo(t *testing.T) {
var (
roleName1 = "foo1"
username1 = "fo1"
username2 = "fo2"
roleName2 = "foo2"
result []*milvuspb.UserResult
err error
)
userDbMock.On("ListUser", tenantID).Return([]*dbmodel.User{{ID: 1, Username: username1}, {ID: 10, Username: username2}}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(1), int64(0)).Return([]*dbmodel.UserRole{{Role: dbmodel.Role{Name: roleName1}}, {Role: dbmodel.Role{Name: roleName2}}}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(10), int64(0)).Return([]*dbmodel.UserRole{}, nil).Once()
result, err = mockCatalog.ListUser(ctx, tenantID, nil, true)
require.NoError(t, err)
require.Equal(t, 2, len(result))
require.Equal(t, username1, result[0].User.Name)
require.Equal(t, 2, len(result[0].Roles))
require.Equal(t, roleName1, result[0].Roles[0].Name)
require.Equal(t, roleName2, result[0].Roles[1].Name)
require.Equal(t, username2, result[1].User.Name)
}
func TestTableCatalog_ListUser_AllUser_Empty(t *testing.T) {
var (
result []*milvuspb.UserResult
err error
)
userDbMock.On("ListUser", tenantID).Return([]*dbmodel.User{}, nil).Once()
result, err = mockCatalog.ListUser(ctx, tenantID, nil, false)
require.NoError(t, err)
require.Equal(t, 0, len(result))
}
func TestTableCatalog_ListUser_OneUser(t *testing.T) {
var (
username1 = "foo1"
result []*milvuspb.UserResult
err error
)
userDbMock.On("GetByUsername", tenantID, username1).Return(&dbmodel.User{Username: username1}, nil).Once()
result, err = mockCatalog.ListUser(ctx, tenantID, &milvuspb.UserEntity{Name: username1}, false)
require.NoError(t, err)
require.Equal(t, 1, len(result))
require.Equal(t, username1, result[0].User.Name)
}
func TestTableCatalog_ListUser_OneUser_IncludeRoleInfo(t *testing.T) {
var (
roleName1 = "foo1"
roleName2 = "foo1"
username1 = "fo1"
result []*milvuspb.UserResult
err error
)
userDbMock.On("GetByUsername", tenantID, username1).Return(&dbmodel.User{ID: 1, Username: username1}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(1), int64(0)).
Return([]*dbmodel.UserRole{{Role: dbmodel.Role{Name: roleName1}}, {Role: dbmodel.Role{Name: roleName2}}}, nil).Once()
result, err = mockCatalog.ListUser(ctx, tenantID, &milvuspb.UserEntity{Name: username1}, true)
require.NoError(t, err)
require.Equal(t, 1, len(result))
require.Equal(t, username1, result[0].User.Name)
require.Equal(t, 2, len(result[0].Roles))
require.Equal(t, roleName1, result[0].Roles[0].Name)
require.Equal(t, roleName2, result[0].Roles[1].Name)
}
func TestTableCatalog_ListUser_ListUserError(t *testing.T) {
userDbMock.On("ListUser", tenantID).Return(nil, errors.New("test error")).Once()
_, err := mockCatalog.ListUser(ctx, tenantID, nil, false)
require.Error(t, err)
}
func TestTableCatalog_ListUser_GetByUsernameError(t *testing.T) {
var (
username1 = "foo"
err error
)
userDbMock.On("GetByUsername", tenantID, username1).Return(nil, errors.New("test error")).Once()
_, err = mockCatalog.ListUser(ctx, tenantID, &milvuspb.UserEntity{Name: username1}, false)
require.Error(t, err)
}
func TestTableCatalog_ListUser_GetUserRolesError(t *testing.T) {
var (
username1 = "foo"
err error
)
userDbMock.On("GetByUsername", tenantID, username1).Return(&dbmodel.User{ID: 1, Username: username1}, nil).Once()
userRoleDbMock.On("GetUserRoles", tenantID, int64(1), int64(0)).Return(nil, errors.New("test error")).Once()
_, err = mockCatalog.ListUser(ctx, tenantID, &milvuspb.UserEntity{Name: username1}, true)
require.Error(t, err)
}
func TestTableCatalog_AlterPrivilege_Revoke(t *testing.T) {
var (
grant *milvuspb.GrantEntity
err error
)
grant = &milvuspb.GrantEntity{
Role: &milvuspb.RoleEntity{Name: "foo"},
Object: &milvuspb.ObjectEntity{Name: "Collection"},
ObjectName: "col1",
Grantor: &milvuspb.GrantorEntity{
User: &milvuspb.UserEntity{Name: "foo"},
Privilege: &milvuspb.PrivilegeEntity{Name: "PrivilegeLoad"},
},
}
roleDbMock.On("GetRoles", tenantID, grant.Role.Name).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: 1}}}, nil).Once()
grantDbMock.On("Delete", tenantID, int64(1), grant.Object.Name, grant.ObjectName, grant.Grantor.Privilege.Name).Return(nil).Once()
err = mockCatalog.AlterGrant(ctx, tenantID, grant, milvuspb.OperatePrivilegeType_Revoke)
require.NoError(t, err)
}
func TestTableCatalog_AlterPrivilege_Grant(t *testing.T) {
var (
grant *milvuspb.GrantEntity
err error
)
grant = &milvuspb.GrantEntity{
Role: &milvuspb.RoleEntity{Name: "foo"},
Object: &milvuspb.ObjectEntity{Name: "Collection"},
ObjectName: "col1",
Grantor: &milvuspb.GrantorEntity{
User: &milvuspb.UserEntity{Name: "foo"},
Privilege: &milvuspb.PrivilegeEntity{Name: "PrivilegeLoad"},
},
}
roleDbMock.On("GetRoles", tenantID, grant.Role.Name).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: 1}}}, nil).Once()
grantDbMock.On("Insert", mock.Anything).Return(nil).Once()
err = mockCatalog.AlterGrant(ctx, tenantID, grant, milvuspb.OperatePrivilegeType_Grant)
require.NoError(t, err)
}
func TestTableCatalog_AlterPrivilege_InvalidType(t *testing.T) {
var (
roleName = "foo"
err error
)
roleDbMock.On("GetRoles", tenantID, roleName).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: 1}}}, nil).Once()
err = mockCatalog.AlterGrant(ctx, tenantID, &milvuspb.GrantEntity{Role: &milvuspb.RoleEntity{Name: roleName}}, 100)
require.Error(t, err)
}
func TestTableCatalog_ListGrant(t *testing.T) {
var (
grant *milvuspb.GrantEntity
grants []*dbmodel.Grant
entites []*milvuspb.GrantEntity
err error
)
grant = &milvuspb.GrantEntity{
Role: &milvuspb.RoleEntity{Name: "foo"},
Object: &milvuspb.ObjectEntity{Name: "Collection"},
ObjectName: "col1",
}
grants = []*dbmodel.Grant{
{
Role: dbmodel.Role{Name: "foo"},
Object: "Collection",
ObjectName: "col1",
Detail: "[[\"admin\",\"PrivilegeIndexDetail\"],[\"admin\",\"PrivilegeLoad\"],[\"admin\",\"*\"]]",
},
}
roleDbMock.On("GetRoles", tenantID, grant.Role.Name).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: 1}}}, nil).Once()
grantDbMock.On("GetGrants", tenantID, int64(1), grant.Object.Name, grant.ObjectName).Return(grants, nil).Once()
entites, err = mockCatalog.ListGrant(ctx, tenantID, grant)
require.NoError(t, err)
require.Equal(t, 3, len(entites))
}
func TestTableCatalog_ListGrant_GetRolesError(t *testing.T) {
var (
grant *milvuspb.GrantEntity
err error
)
grant = &milvuspb.GrantEntity{
Role: &milvuspb.RoleEntity{Name: "foo"},
Object: &milvuspb.ObjectEntity{Name: "Collection"},
ObjectName: "col1",
}
roleDbMock.On("GetRoles", tenantID, grant.Role.Name).Return(nil, errors.New("test error")).Once()
_, err = mockCatalog.ListGrant(ctx, tenantID, grant)
require.Error(t, err)
}
func TestTableCatalog_ListGrant_GetGrantError(t *testing.T) {
var (
grant *milvuspb.GrantEntity
err error
)
grant = &milvuspb.GrantEntity{
Role: &milvuspb.RoleEntity{Name: "foo"},
Object: &milvuspb.ObjectEntity{Name: "Collection"},
ObjectName: "col1",
}
roleDbMock.On("GetRoles", tenantID, grant.Role.Name).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: 1}}}, nil).Once()
grantDbMock.On("GetGrants", tenantID, int64(1), grant.Object.Name, grant.ObjectName).Return(nil, errors.New("test error")).Once()
_, err = mockCatalog.ListGrant(ctx, tenantID, grant)
require.Error(t, err)
}
func TestTableCatalog_ListGrant_DecodeError(t *testing.T) {
var (
grant *milvuspb.GrantEntity
grants []*dbmodel.Grant
err error
)
grant = &milvuspb.GrantEntity{
Role: &milvuspb.RoleEntity{Name: "foo"},
Object: &milvuspb.ObjectEntity{Name: "Collection"},
ObjectName: "col1",
}
grants = []*dbmodel.Grant{
{
Role: dbmodel.Role{Name: "foo"},
Object: "Collection",
ObjectName: "col1",
Detail: "decode error",
},
}
roleDbMock.On("GetRoles", tenantID, grant.Role.Name).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: 1}}}, nil).Once()
grantDbMock.On("GetGrants", tenantID, int64(1), grant.Object.Name, grant.ObjectName).Return(grants, nil).Once()
_, err = mockCatalog.ListGrant(ctx, tenantID, grant)
require.Error(t, err)
}
func TestTableCatalog_ListGrant_DetailLenError(t *testing.T) {
var (
grant *milvuspb.GrantEntity
grants []*dbmodel.Grant
err error
)
grant = &milvuspb.GrantEntity{
Role: &milvuspb.RoleEntity{Name: "foo"},
Object: &milvuspb.ObjectEntity{Name: "Collection"},
ObjectName: "col1",
}
grants = []*dbmodel.Grant{
{
Role: dbmodel.Role{Name: "foo"},
Object: "Collection",
ObjectName: "col1",
Detail: "[[\"admin\"]]",
},
}
roleDbMock.On("GetRoles", tenantID, grant.Role.Name).Return([]*dbmodel.Role{{Base: dbmodel.Base{ID: 1}}}, nil).Once()
grantDbMock.On("GetGrants", tenantID, int64(1), grant.Object.Name, grant.ObjectName).Return(grants, nil).Once()
_, err = mockCatalog.ListGrant(ctx, tenantID, grant)
require.Error(t, err)
}
func TestTableCatalog_ListPolicy(t *testing.T) {
//TODO implement me
_, gotErr := mockCatalog.ListPolicy(ctx, tenantID)
require.Nil(t, gotErr)
var (
grant *milvuspb.GrantEntity
grants []*dbmodel.Grant
policies []string
err error
)
grant = &milvuspb.GrantEntity{
Role: &milvuspb.RoleEntity{Name: "foo"},
Object: &milvuspb.ObjectEntity{Name: "Collection"},
ObjectName: "col1",
}
grants = []*dbmodel.Grant{
{
Role: dbmodel.Role{Name: "foo"},
Object: "Collection",
ObjectName: "col1",
Detail: "[[\"admin\",\"PrivilegeIndexDetail\"]]",
},
}
grantDbMock.On("GetGrants", tenantID, int64(0), "", "").Return(grants, nil).Once()
policies, err = mockCatalog.ListPolicy(ctx, tenantID)
require.NoError(t, err)
require.Equal(t, 1, len(policies))
require.Equal(t, funcutil.PolicyForPrivilege(grant.Role.Name, grant.Object.Name, grant.ObjectName, "PrivilegeIndexDetail"), policies[0])
}
func TestTableCatalog_ListPolicy_GetGrantsError(t *testing.T) {
grantDbMock.On("GetGrants", tenantID, int64(0), "", "").Return(nil, errors.New("test error")).Once()
_, err := mockCatalog.ListPolicy(ctx, tenantID)
require.Error(t, err)
}
func TestTableCatalog_ListPolicy_DetailLenError(t *testing.T) {
var (
grants []*dbmodel.Grant
err error
)
grants = []*dbmodel.Grant{
{
Role: dbmodel.Role{Name: "foo"},
Object: "Collection",
ObjectName: "col1",
Detail: "decode error",
},
}
grantDbMock.On("GetGrants", tenantID, int64(0), "", "").Return(grants, nil).Once()
_, err = mockCatalog.ListPolicy(ctx, tenantID)
require.Error(t, err)
}
func TestTableCatalog_ListPolicy_DecodeError(t *testing.T) {
var (
grants []*dbmodel.Grant
err error
)
grants = []*dbmodel.Grant{
{
Role: dbmodel.Role{Name: "foo"},
Object: "Collection",
ObjectName: "col1",
Detail: "[[\"admin\"]]",
},
}
grantDbMock.On("GetGrants", tenantID, int64(0), "", "").Return(grants, nil).Once()
_, err = mockCatalog.ListPolicy(ctx, tenantID)
require.Error(t, err)
}
func TestTableCatalog_ListUserRole(t *testing.T) {
//TODO implement me
_, gotErr := mockCatalog.ListUserRole(ctx, tenantID)
require.Nil(t, gotErr)
var (
username1 = "foo1"
username2 = "foo2"
roleName1 = "fo1"
roleName2 = "fo2"
userRoles []string
err error
)
userRoleDbMock.On("GetUserRoles", tenantID, int64(0), int64(0)).Return([]*dbmodel.UserRole{
{User: dbmodel.User{Username: username1}, Role: dbmodel.Role{Name: roleName1}},
{User: dbmodel.User{Username: username2}, Role: dbmodel.Role{Name: roleName2}},
}, nil).Once()
userRoles, err = mockCatalog.ListUserRole(ctx, tenantID)
require.NoError(t, err)
require.Equal(t, 2, len(userRoles))
require.Equal(t, funcutil.EncodeUserRoleCache(username1, roleName1), userRoles[0])
}
func TestTableCatalog_ListUserRole_Error(t *testing.T) {
userRoleDbMock.On("GetUserRoles", tenantID, int64(0), int64(0)).Return(nil, errors.New("test error")).Once()
_, err := mockCatalog.ListUserRole(ctx, tenantID)
require.Error(t, err)
}

View File

@ -784,14 +784,35 @@ func (kc *Catalog) ListCredentials(ctx context.Context) ([]string, error) {
return usernames, nil
}
func (kc *Catalog) CreateRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity) error {
k := funcutil.HandleTenantForEtcdKey(RolePrefix, tenant, entity.Name)
err := kc.Txn.Save(k, "")
if err != nil {
log.Error("fail to create role", zap.String("key", k), zap.Error(err))
func (kc *Catalog) save(k string) error {
var err error
if _, err = kc.Txn.Load(k); err != nil && !common.IsKeyNotExistError(err) {
return err
}
return nil
if err == nil {
return common.NewIgnorableError(fmt.Errorf("the key[%s] is existed", k))
}
return kc.Txn.Save(k, "")
}
func (kc *Catalog) remove(k string) error {
var err error
if _, err = kc.Txn.Load(k); err != nil {
return err
}
if common.IsKeyNotExistError(err) {
return common.NewIgnorableError(fmt.Errorf("the key[%s] isn't existed", k))
}
return kc.Txn.Remove(k)
}
func (kc *Catalog) CreateRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity) error {
k := funcutil.HandleTenantForEtcdKey(RolePrefix, tenant, entity.Name)
err := kc.save(k)
if err != nil {
log.Error("fail to save the role", zap.String("key", k), zap.Error(err))
}
return err
}
func (kc *Catalog) DropRole(ctx context.Context, tenant string, roleName string) error {
@ -804,18 +825,18 @@ func (kc *Catalog) DropRole(ctx context.Context, tenant string, roleName string)
return nil
}
func (kc *Catalog) OperateUserRole(ctx context.Context, tenant string, userEntity *milvuspb.UserEntity, roleEntity *milvuspb.RoleEntity, operateType milvuspb.OperateUserRoleType) error {
func (kc *Catalog) AlterUserRole(ctx context.Context, tenant string, userEntity *milvuspb.UserEntity, roleEntity *milvuspb.RoleEntity, operateType milvuspb.OperateUserRoleType) error {
k := funcutil.HandleTenantForEtcdKey(RoleMappingPrefix, tenant, fmt.Sprintf("%s/%s", userEntity.Name, roleEntity.Name))
var err error
if operateType == milvuspb.OperateUserRoleType_AddUserToRole {
err = kc.Txn.Save(k, "")
err = kc.save(k)
if err != nil {
log.Error("fail to add user to role", zap.String("key", k), zap.Error(err))
log.Error("fail to save the user-role", zap.String("key", k), zap.Error(err))
}
} else if operateType == milvuspb.OperateUserRoleType_RemoveUserFromRole {
err = kc.Txn.Remove(k)
err = kc.remove(k)
if err != nil {
log.Error("fail to remove user from role", zap.String("key", k), zap.Error(err))
log.Error("fail to remove the user-role", zap.String("key", k), zap.Error(err))
}
} else {
err = fmt.Errorf("invalid operate user role type, operate type: %d", operateType)
@ -823,7 +844,7 @@ func (kc *Catalog) OperateUserRole(ctx context.Context, tenant string, userEntit
return err
}
func (kc *Catalog) SelectRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) {
func (kc *Catalog) ListRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) {
var results []*milvuspb.RoleResult
roleToUsers := make(map[string][]string)
@ -927,7 +948,7 @@ func (kc *Catalog) getUserResult(tenant string, username string, includeRoleInfo
return result, nil
}
func (kc *Catalog) SelectUser(ctx context.Context, tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error) {
func (kc *Catalog) ListUser(ctx context.Context, tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error) {
var (
usernames []string
err error
@ -967,7 +988,7 @@ func (kc *Catalog) SelectUser(ctx context.Context, tenant string, entity *milvus
return results, nil
}
func (kc *Catalog) OperatePrivilege(ctx context.Context, tenant string, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error {
func (kc *Catalog) AlterGrant(ctx context.Context, tenant string, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error {
privilegeName := entity.Grantor.Privilege.Name
k := funcutil.HandleTenantForEtcdKey(GranteePrefix, tenant, fmt.Sprintf("%s/%s/%s", entity.Role.Name, entity.Object.Name, entity.ObjectName))
@ -976,6 +997,9 @@ func (kc *Catalog) OperatePrivilege(ctx context.Context, tenant string, entity *
if err != nil {
log.Warn("fail to load grant privilege entity", zap.String("key", k), zap.Any("type", operateType), zap.Error(err))
if funcutil.IsRevoke(operateType) {
if common.IsKeyNotExistError(err) {
return common.NewIgnorableError(fmt.Errorf("the grant[%s] isn't existed", k))
}
return err
}
if !common.IsKeyNotExistError(err) {
@ -1007,9 +1031,9 @@ func (kc *Catalog) OperatePrivilege(ctx context.Context, tenant string, entity *
User: &milvuspb.UserEntity{Name: entity.Grantor.User.Name},
})
} else if isExisted && funcutil.IsGrant(operateType) {
return nil
return common.NewIgnorableError(fmt.Errorf("the privilege[%s] is granted", privilegeName))
} else if !isExisted && funcutil.IsRevoke(operateType) {
return fmt.Errorf("fail to revoke the privilege because the privilege isn't granted for the role, key: /%s", k)
return common.NewIgnorableError(fmt.Errorf("the privilege[%s] isn't granted", privilegeName))
} else if isExisted && funcutil.IsRevoke(operateType) {
curGrantPrivilegeEntity.Entities = append(curGrantPrivilegeEntity.Entities[:dropIndex], curGrantPrivilegeEntity.Entities[dropIndex+1:]...)
}
@ -1037,7 +1061,7 @@ func (kc *Catalog) OperatePrivilege(ctx context.Context, tenant string, entity *
return nil
}
func (kc *Catalog) SelectGrant(ctx context.Context, tenant string, entity *milvuspb.GrantEntity) ([]*milvuspb.GrantEntity, error) {
func (kc *Catalog) ListGrant(ctx context.Context, tenant string, entity *milvuspb.GrantEntity) ([]*milvuspb.GrantEntity, error) {
var entities []*milvuspb.GrantEntity
var k string

View File

@ -4,11 +4,11 @@ import (
"context"
"strings"
"google.golang.org/grpc/metadata"
"github.com/milvus-io/milvus/internal/util"
"github.com/milvus-io/milvus/internal/util/crypto"
"google.golang.org/grpc/metadata"
)
// validAuth validates the authentication

View File

@ -1370,21 +1370,21 @@ func (mt *MetaTable) OperateUserRole(tenant string, userEntity *milvuspb.UserEnt
return fmt.Errorf("role name in the role entity is empty")
}
return mt.catalog.OperateUserRole(mt.ctx, tenant, userEntity, roleEntity, operateType)
return mt.catalog.AlterUserRole(mt.ctx, tenant, userEntity, roleEntity, operateType)
}
// SelectRole select role.
// Enter the role condition by the entity param. And this param is nil, which means selecting all roles.
// Get all users that are added to the role by setting the includeUserInfo param to true.
func (mt *MetaTable) SelectRole(tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) {
return mt.catalog.SelectRole(mt.ctx, tenant, entity, includeUserInfo)
return mt.catalog.ListRole(mt.ctx, tenant, entity, includeUserInfo)
}
// SelectUser select user.
// Enter the user condition by the entity param. And this param is nil, which means selecting all users.
// Get all roles that are added the user to by setting the includeRoleInfo param to true.
func (mt *MetaTable) SelectUser(tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error) {
return mt.catalog.SelectUser(mt.ctx, tenant, entity, includeRoleInfo)
return mt.catalog.ListUser(mt.ctx, tenant, entity, includeRoleInfo)
}
// OperatePrivilege grant or revoke privilege by setting the operateType param
@ -1411,7 +1411,7 @@ func (mt *MetaTable) OperatePrivilege(tenant string, entity *milvuspb.GrantEntit
return fmt.Errorf("the operate type in the grant entity is invalid")
}
return mt.catalog.OperatePrivilege(mt.ctx, tenant, entity, operateType)
return mt.catalog.AlterGrant(mt.ctx, tenant, entity, operateType)
}
// SelectGrant select grant
@ -1422,7 +1422,7 @@ func (mt *MetaTable) SelectGrant(tenant string, entity *milvuspb.GrantEntity) ([
if entity.Role == nil || funcutil.IsEmptyString(entity.Role.Name) {
return entities, fmt.Errorf("the role entity in the grant entity is invalid")
}
return mt.catalog.SelectGrant(mt.ctx, tenant, entity)
return mt.catalog.ListGrant(mt.ctx, tenant, entity)
}
func (mt *MetaTable) ListPolicy(tenant string) ([]string, error) {

View File

@ -1087,9 +1087,24 @@ func TestRbacCreateRole(t *testing.T) {
mockTxnKV.save = func(key, value string) error {
return nil
}
mockTxnKV.load = func(key string) (string, error) {
return "", common.NewKeyNotExistError(key)
}
err = mt.CreateRole(util.DefaultTenant, &milvuspb.RoleEntity{Name: "role1"})
assert.Nil(t, err)
mockTxnKV.load = func(key string) (string, error) {
return "", fmt.Errorf("load error")
}
err = mt.CreateRole(util.DefaultTenant, &milvuspb.RoleEntity{Name: "role1"})
assert.NotNil(t, err)
mockTxnKV.load = func(key string) (string, error) {
return "", nil
}
err = mt.CreateRole(util.DefaultTenant, &milvuspb.RoleEntity{Name: "role1"})
assert.Equal(t, true, common.IsIgnorableError(err))
mockTxnKV.save = func(key, value string) error {
return fmt.Errorf("save error")
}
@ -1134,6 +1149,9 @@ func TestRbacOperateRole(t *testing.T) {
err = mt.OperateUserRole(util.DefaultTenant, &milvuspb.UserEntity{Name: "user"}, &milvuspb.RoleEntity{Name: "role"}, milvuspb.OperateUserRoleType(100))
assert.NotNil(t, err)
mockTxnKV.load = func(key string) (string, error) {
return "", common.NewKeyNotExistError(key)
}
err = mt.OperateUserRole(util.DefaultTenant, &milvuspb.UserEntity{Name: "user"}, &milvuspb.RoleEntity{Name: "role"}, milvuspb.OperateUserRoleType_AddUserToRole)
assert.Nil(t, err)
@ -1146,6 +1164,9 @@ func TestRbacOperateRole(t *testing.T) {
mockTxnKV.remove = func(key string) error {
return nil
}
mockTxnKV.load = func(key string) (string, error) {
return "", nil
}
err = mt.OperateUserRole(util.DefaultTenant, &milvuspb.UserEntity{Name: "user"}, &milvuspb.RoleEntity{Name: "role"}, milvuspb.OperateUserRoleType_RemoveUserFromRole)
assert.Nil(t, err)
@ -1369,11 +1390,10 @@ func TestRbacOperatePrivilege(t *testing.T) {
return string(grantPrivilegeEntityByte), nil
}
err = mt.OperatePrivilege(util.DefaultTenant, entity, milvuspb.OperatePrivilegeType_Grant)
assert.Nil(t, err)
assert.Equal(t, true, common.IsIgnorableError(err))
entity.Grantor.Privilege = &milvuspb.PrivilegeEntity{Name: commonpb.ObjectPrivilege_PrivilegeRelease.String()}
err = mt.OperatePrivilege(util.DefaultTenant, entity, milvuspb.OperatePrivilegeType_Revoke)
assert.NotNil(t, err)
entity.Grantor.Privilege = &milvuspb.PrivilegeEntity{Name: commonpb.ObjectPrivilege_PrivilegeLoad.String()}
assert.Equal(t, true, common.IsIgnorableError(err))
grantPrivilegeEntity = &milvuspb.GrantPrivilegeEntity{Entities: []*milvuspb.GrantorEntity{
{User: &milvuspb.UserEntity{Name: "user2"}, Privilege: &milvuspb.PrivilegeEntity{Name: commonpb.ObjectPrivilege_PrivilegeLoad.String()}},
}}
@ -1383,6 +1403,13 @@ func TestRbacOperatePrivilege(t *testing.T) {
}
err = mt.OperatePrivilege(util.DefaultTenant, entity, milvuspb.OperatePrivilegeType_Grant)
assert.Nil(t, err)
grantPrivilegeEntity = &milvuspb.GrantPrivilegeEntity{Entities: []*milvuspb.GrantorEntity{
{User: &milvuspb.UserEntity{Name: "user2"}, Privilege: &milvuspb.PrivilegeEntity{Name: commonpb.ObjectPrivilege_PrivilegeRelease.String()}},
}}
mockTxnKV.load = func(key string) (string, error) {
grantPrivilegeEntityByte, _ := proto.Marshal(grantPrivilegeEntity)
return string(grantPrivilegeEntityByte), nil
}
mockTxnKV.remove = func(key string) error {
return fmt.Errorf("remove error")
}

View File

@ -1292,11 +1292,14 @@ func (c *Core) initData() error {
func (c *Core) initRbac() (initError error) {
// create default roles, including admin, public
if initError = c.MetaTable.CreateRole(util.DefaultTenant, &milvuspb.RoleEntity{Name: util.RoleAdmin}); initError != nil {
return
}
if initError = c.MetaTable.CreateRole(util.DefaultTenant, &milvuspb.RoleEntity{Name: util.RolePublic}); initError != nil {
return
for _, role := range util.DefaultRoles {
if initError = c.MetaTable.CreateRole(util.DefaultTenant, &milvuspb.RoleEntity{Name: role}); initError != nil {
if common.IsIgnorableError(initError) {
initError = nil
continue
}
return
}
}
// grant privileges for the public role
@ -1318,6 +1321,10 @@ func (c *Core) initRbac() (initError error) {
Privilege: &milvuspb.PrivilegeEntity{Name: globalPrivilege},
},
}, milvuspb.OperatePrivilegeType_Grant); initError != nil {
if common.IsIgnorableError(initError) {
initError = nil
continue
}
return
}
}
@ -1331,6 +1338,10 @@ func (c *Core) initRbac() (initError error) {
Privilege: &milvuspb.PrivilegeEntity{Name: collectionPrivilege},
},
}, milvuspb.OperatePrivilegeType_Grant); initError != nil {
if common.IsIgnorableError(initError) {
initError = nil
continue
}
return
}
}
@ -2973,14 +2984,6 @@ func (c *Core) CreateRole(ctx context.Context, in *milvuspb.CreateRoleRequest) (
return errorutil.UnhealthyStatus(code), errorutil.UnhealthyError()
}
entity := in.Entity
_, err := c.MetaTable.SelectRole(util.DefaultTenant, &milvuspb.RoleEntity{Name: entity.Name}, false)
if err == nil {
errMsg := "role already exists:" + entity.Name
return failStatus(commonpb.ErrorCode_CreateRoleFailure, errMsg), errors.New(errMsg)
}
if !common.IsKeyNotExistError(err) {
return failStatus(commonpb.ErrorCode_CreateRoleFailure, err.Error()), err
}
results, err := c.MetaTable.SelectRole(util.DefaultTenant, nil, false)
if err != nil {
@ -3045,6 +3048,9 @@ func (c *Core) DropRole(ctx context.Context, in *milvuspb.DropRoleRequest) (*com
for _, roleResult := range roleResults {
for index, userEntity := range roleResult.Users {
if err = c.MetaTable.OperateUserRole(util.DefaultTenant, &milvuspb.UserEntity{Name: userEntity.Name}, &milvuspb.RoleEntity{Name: roleResult.Role.Name}, milvuspb.OperateUserRoleType_RemoveUserFromRole); err != nil {
if common.IsIgnorableError(err) {
continue
}
errMsg := "fail to remove user from role"
logger.Error(errMsg, zap.String("role_name", roleResult.Role.Name), zap.String("username", userEntity.Name), zap.Int("current_index", index), zap.Error(err))
return failStatus(commonpb.ErrorCode_OperateUserRoleFailure, errMsg), err
@ -3090,23 +3096,34 @@ func (c *Core) OperateUserRole(ctx context.Context, in *milvuspb.OperateUserRole
logger.Error(errMsg, zap.String("username", in.Username), zap.Error(err))
return failStatus(commonpb.ErrorCode_OperateUserRoleFailure, errMsg), err
}
updateCache := true
if err := c.MetaTable.OperateUserRole(util.DefaultTenant, &milvuspb.UserEntity{Name: in.Username}, &milvuspb.RoleEntity{Name: in.RoleName}, in.Type); err != nil {
errMsg := "fail to operate user to role"
logger.Error(errMsg, zap.String("role_name", in.RoleName), zap.String("username", in.Username), zap.Error(err))
return failStatus(commonpb.ErrorCode_OperateUserRoleFailure, errMsg), err
if !common.IsIgnorableError(err) {
errMsg := "fail to operate user to role"
logger.Error(errMsg, zap.String("role_name", in.RoleName), zap.String("username", in.Username), zap.Error(err))
return failStatus(commonpb.ErrorCode_OperateUserRoleFailure, errMsg), err
}
updateCache = false
}
var opType int32
if in.Type == milvuspb.OperateUserRoleType_AddUserToRole {
opType = int32(typeutil.CacheAddUserToRole)
} else if in.Type == milvuspb.OperateUserRoleType_RemoveUserFromRole {
opType = int32(typeutil.CacheRemoveUserFromRole)
}
if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{
OpType: opType,
OpKey: funcutil.EncodeUserRoleCache(in.Username, in.RoleName),
}); err != nil {
return failStatus(commonpb.ErrorCode_OperateUserRoleFailure, err.Error()), err
if updateCache {
var opType int32
switch in.Type {
case milvuspb.OperateUserRoleType_AddUserToRole:
opType = int32(typeutil.CacheAddUserToRole)
case milvuspb.OperateUserRoleType_RemoveUserFromRole:
opType = int32(typeutil.CacheRemoveUserFromRole)
default:
errMsg := "invalid operate type for the OperateUserRole api"
logger.Error(errMsg, zap.Any("op_type", in.Type))
return failStatus(commonpb.ErrorCode_OperateUserRoleFailure, errMsg), errors.New(errMsg)
}
if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{
OpType: opType,
OpKey: funcutil.EncodeUserRoleCache(in.Username, in.RoleName),
}); err != nil {
return failStatus(commonpb.ErrorCode_OperateUserRoleFailure, err.Error()), err
}
}
logger.Debug(method + " success")
@ -3306,23 +3323,34 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile
if in.Entity.Object.Name == commonpb.ObjectType_Global.String() {
in.Entity.ObjectName = util.AnyWord
}
updateCache := true
if err := c.MetaTable.OperatePrivilege(util.DefaultTenant, in.Entity, in.Type); err != nil {
errMsg := "fail to operate the privilege"
logger.Error(errMsg, zap.Error(err))
return failStatus(commonpb.ErrorCode_OperatePrivilegeFailure, errMsg), err
if !common.IsIgnorableError(err) {
errMsg := "fail to operate the privilege"
logger.Error(errMsg, zap.Error(err))
return failStatus(commonpb.ErrorCode_OperatePrivilegeFailure, errMsg), err
}
updateCache = false
}
var opType int32
if in.Type == milvuspb.OperatePrivilegeType_Grant {
opType = int32(typeutil.CacheGrantPrivilege)
} else if in.Type == milvuspb.OperatePrivilegeType_Revoke {
opType = int32(typeutil.CacheRevokePrivilege)
}
if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{
OpType: opType,
OpKey: funcutil.PolicyForPrivilege(in.Entity.Role.Name, in.Entity.Object.Name, in.Entity.ObjectName, in.Entity.Grantor.Privilege.Name),
}); err != nil {
return failStatus(commonpb.ErrorCode_OperatePrivilegeFailure, err.Error()), err
if updateCache {
var opType int32
switch in.Type {
case milvuspb.OperatePrivilegeType_Grant:
opType = int32(typeutil.CacheGrantPrivilege)
case milvuspb.OperatePrivilegeType_Revoke:
opType = int32(typeutil.CacheRevokePrivilege)
default:
errMsg := "invalid operate type for the OperatePrivilege api"
logger.Error(errMsg, zap.Any("op_type", in.Type))
return failStatus(commonpb.ErrorCode_OperatePrivilegeFailure, errMsg), errors.New(errMsg)
}
if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{
OpType: opType,
OpKey: funcutil.PolicyForPrivilege(in.Entity.Role.Name, in.Entity.Object.Name, in.Entity.ObjectName, in.Entity.Grantor.Privilege.Name),
}); err != nil {
return failStatus(commonpb.ErrorCode_OperatePrivilegeFailure, err.Error()), err
}
}
logger.Debug(method + " success")
@ -3367,6 +3395,11 @@ func (c *Core) SelectGrant(ctx context.Context, in *milvuspb.SelectGrantRequest)
}
grantEntities, err := c.MetaTable.SelectGrant(util.DefaultTenant, in.Entity)
if common.IsKeyNotExistError(err) {
return &milvuspb.SelectGrantResponse{
Status: succStatus(),
}, nil
}
if err != nil {
errMsg := "fail to select the grant"
logger.Error(errMsg, zap.Error(err))

View File

@ -60,5 +60,6 @@ go test -race -cover ${APPLE_SILICON_FLAG} "${MILVUS_DIR}/distributed/querynode/
go test -race -cover ${APPLE_SILICON_FLAG} "${MILVUS_DIR}/rootcoord" -failfast
go test -race -cover ${APPLE_SILICON_FLAG} "${MILVUS_DIR}/datacoord/..." -failfast
go test -race -cover ${APPLE_SILICON_FLAG} "${MILVUS_DIR}/indexcoord/..." -failfast
go test -race -cover ${APPLE_SILICON_FLAG} "${MILVUS_DIR}/metastore/..." -failfast
echo " Go unittest finished"

View File

@ -210,3 +210,43 @@ CREATE TABLE if not exists milvus_meta.credential_users (
PRIMARY KEY (id),
INDEX idx_tenant_id_username (tenant_id, username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- role
CREATE TABLE if not exists milvus_meta.role (
id BIGINT NOT NULL AUTO_INCREMENT,
tenant_id VARCHAR(128) DEFAULT NULL,
name VARCHAR(128) NOT NULL,
is_deleted BOOL NOT NULL DEFAULT false,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP on update current_timestamp,
INDEX idx_role_tenant_name (tenant_id, name),
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- user-role
CREATE TABLE if not exists milvus_meta.user_role (
id BIGINT NOT NULL AUTO_INCREMENT,
tenant_id VARCHAR(128) DEFAULT NULL,
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
is_deleted BOOL NOT NULL DEFAULT false,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP on update current_timestamp,
INDEX idx_role_mapping_tenant_user_role (tenant_id, user_id, role_id),
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- grant
CREATE TABLE if not exists milvus_meta.grant (
id BIGINT NOT NULL AUTO_INCREMENT,
tenant_id VARCHAR(128) DEFAULT NULL,
role_id BIGINT NOT NULL,
object VARCHAR(128) NOT NULL,
object_name VARCHAR(128) NOT NULL,
detail TEXT NOT NULL,
is_deleted BOOL NOT NULL DEFAULT false,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP on update current_timestamp,
INDEX idx_grant_principal_resource_tenant (tenant_id, role_id, object, object_name),
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;