未验证 提交 ce434b49 编写于 作者: S SimFG 提交者: GitHub

Implement the mysql metastore of the rbac (#18704)

Signed-off-by: NSimFG <bang.fu@zilliz.com>
Signed-off-by: NSimFG <bang.fu@zilliz.com>
上级 94ffa8a2
......@@ -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
......
......@@ -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)
......
......@@ -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()
}
......@@ -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)}
}
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
}
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))
}
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
}
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)
}
......@@ -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 {
......
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
}
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)
}
......@@ -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)
}
......
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"`
}
......@@ -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,
}
}
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
}
// 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
}
......@@ -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())
......
// 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
}
......@@ -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)
}
}
......
// 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
}
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
}
......@@ -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
}
......
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
}
......@@ -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() {
......
......@@ -784,14 +784,35 @@ func (kc *Catalog) ListCredentials(ctx context.Context) ([]string, error) {
return usernames, nil
}
func (kc *Catalog) save(k string) error {
var err error
if _, err = kc.Txn.Load(k); err != nil && !common.IsKeyNotExistError(err) {
return err
}
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.Txn.Save(k, "")
err := kc.save(k)
if err != nil {
log.Error("fail to create role", zap.String("key", k), zap.Error(err))
return err
log.Error("fail to save the role", zap.String("key", k), zap.Error(err))
}
return nil
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
......
......@@ -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
......
......@@ -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) {
......
......@@ -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")
}
......
......@@ -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))
......
......@@ -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"
......@@ -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;
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册