feat: [RBAC] support user description in credentials (#50186)

- Wire credential descriptions through create/update/read paths,
including proxy, HTTP v2, Go SDK options, length validation, optional
update presence handling, and internal credential proto/model
persistence.
- Preserve existing encrypted passwords and descriptions during
RootCoord credential updates when either field is omitted, reject
all-empty or partial direct password updates, and keep sha256 passwords
cache-only.
- Skip auth-cache refresh for description-only credential updates so
existing authentication state is not blanked, while still refreshing
caches for password updates.
- Return persisted user descriptions from select_user and HTTP v2
describe_user, including include_role_info=false, and reuse
already-loaded credential rows in user listing and RBAC backup to avoid
duplicate etcd reads while ignoring malformed credential keys.
- Use the upstream milvus-proto go-api/v3 version that includes the
credential description proto change; the temporary fork replaces in
root, pkg, client, and tests/go_client modules have been removed.

design doc:
docs/design-docs/design_docs/20260601-rbac-user-description.md

related: #50179

---------

Signed-off-by: shaoting-huang <shaoting.huang@zilliz.com>
This commit is contained in:
sthuang
2026-06-15 00:44:22 +08:00
committed by GitHub
parent c45cd7c25c
commit 5d80a9fd81
40 changed files with 1856 additions and 536 deletions
+1 -1
View File
@@ -30,4 +30,4 @@ unittest:
generate-mockery:
@echo "Generating mockery Milvus service server"
@../bin/mockery --srcpkg=github.com/milvus-io/milvus-proto/go-api/v2/milvuspb --name=MilvusServiceServer --filename=mock_milvus_server_test.go --output=milvusclient --outpkg=milvusclient --with-expecter
@../bin/mockery --srcpkg=github.com/milvus-io/milvus-proto/go-api/v3/milvuspb --name=MilvusServiceServer --filename=mock_milvus_server_test.go --output=milvusclient --outpkg=milvusclient --with-expecter
+3 -2
View File
@@ -17,8 +17,9 @@
package entity
type User struct {
UserName string
Roles []string
UserName string
Roles []string
Description string
}
type Role struct {
+1 -1
View File
@@ -7,7 +7,7 @@ require (
github.com/cockroachdb/errors v1.9.1
github.com/google/uuid v1.6.0
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260526093827-489331a5a41f
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260612100615-43795e8f8f6e
github.com/milvus-io/milvus/pkg/v3 v3.0.0-beta
github.com/quasilyte/go-ruleguard/dsl v0.3.23
github.com/samber/lo v1.52.0
+2 -2
View File
@@ -222,8 +222,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260526093827-489331a5a41f h1:W+paUzc+AA381Rydx2DJffoQuM4l+efVqO+2i9GdfMw=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260526093827-489331a5a41f/go.mod h1:rbKpv5JToISTKTTLl0duL5r6wbYnjJ9SsD0QgXMzKy0=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260612100615-43795e8f8f6e h1:axPnOhjsWqqxk0dOQRSIU2mdg52Kyl/8E6iDFZy/IpE=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260612100615-43795e8f8f6e/go.mod h1:rbKpv5JToISTKTTLl0duL5r6wbYnjJ9SsD0QgXMzKy0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+177 -3
View File
@@ -6,11 +6,8 @@ import (
context "context"
commonpb "github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
federpb "github.com/milvus-io/milvus-proto/go-api/v3/federpb"
milvuspb "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb"
mock "github.com/stretchr/testify/mock"
)
@@ -794,6 +791,65 @@ func (_c *MilvusServiceServer_AlterIndex_Call) RunAndReturn(run func(context.Con
return _c
}
// AlterRole provides a mock function with given fields: _a0, _a1
func (_m *MilvusServiceServer) AlterRole(_a0 context.Context, _a1 *milvuspb.AlterRoleRequest) (*commonpb.Status, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for AlterRole")
}
var r0 *commonpb.Status
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.AlterRoleRequest) (*commonpb.Status, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.AlterRoleRequest) *commonpb.Status); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*commonpb.Status)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.AlterRoleRequest) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MilvusServiceServer_AlterRole_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AlterRole'
type MilvusServiceServer_AlterRole_Call struct {
*mock.Call
}
// AlterRole is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *milvuspb.AlterRoleRequest
func (_e *MilvusServiceServer_Expecter) AlterRole(_a0 interface{}, _a1 interface{}) *MilvusServiceServer_AlterRole_Call {
return &MilvusServiceServer_AlterRole_Call{Call: _e.mock.On("AlterRole", _a0, _a1)}
}
func (_c *MilvusServiceServer_AlterRole_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.AlterRoleRequest)) *MilvusServiceServer_AlterRole_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*milvuspb.AlterRoleRequest))
})
return _c
}
func (_c *MilvusServiceServer_AlterRole_Call) Return(_a0 *commonpb.Status, _a1 error) *MilvusServiceServer_AlterRole_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MilvusServiceServer_AlterRole_Call) RunAndReturn(run func(context.Context, *milvuspb.AlterRoleRequest) (*commonpb.Status, error)) *MilvusServiceServer_AlterRole_Call {
_c.Call.Return(run)
return _c
}
// BackupRBAC provides a mock function with given fields: _a0, _a1
func (_m *MilvusServiceServer) BackupRBAC(_a0 context.Context, _a1 *milvuspb.BackupRBACMetaRequest) (*milvuspb.BackupRBACMetaResponse, error) {
ret := _m.Called(_a0, _a1)
@@ -3247,6 +3303,65 @@ func (_c *MilvusServiceServer_DumpMessages_Call) RunAndReturn(run func(*milvuspb
return _c
}
// ExportSnapshot provides a mock function with given fields: _a0, _a1
func (_m *MilvusServiceServer) ExportSnapshot(_a0 context.Context, _a1 *milvuspb.ExportSnapshotRequest) (*milvuspb.ExportSnapshotResponse, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for ExportSnapshot")
}
var r0 *milvuspb.ExportSnapshotResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.ExportSnapshotRequest) (*milvuspb.ExportSnapshotResponse, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.ExportSnapshotRequest) *milvuspb.ExportSnapshotResponse); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*milvuspb.ExportSnapshotResponse)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.ExportSnapshotRequest) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MilvusServiceServer_ExportSnapshot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExportSnapshot'
type MilvusServiceServer_ExportSnapshot_Call struct {
*mock.Call
}
// ExportSnapshot is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *milvuspb.ExportSnapshotRequest
func (_e *MilvusServiceServer_Expecter) ExportSnapshot(_a0 interface{}, _a1 interface{}) *MilvusServiceServer_ExportSnapshot_Call {
return &MilvusServiceServer_ExportSnapshot_Call{Call: _e.mock.On("ExportSnapshot", _a0, _a1)}
}
func (_c *MilvusServiceServer_ExportSnapshot_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.ExportSnapshotRequest)) *MilvusServiceServer_ExportSnapshot_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*milvuspb.ExportSnapshotRequest))
})
return _c
}
func (_c *MilvusServiceServer_ExportSnapshot_Call) Return(_a0 *milvuspb.ExportSnapshotResponse, _a1 error) *MilvusServiceServer_ExportSnapshot_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MilvusServiceServer_ExportSnapshot_Call) RunAndReturn(run func(context.Context, *milvuspb.ExportSnapshotRequest) (*milvuspb.ExportSnapshotResponse, error)) *MilvusServiceServer_ExportSnapshot_Call {
_c.Call.Return(run)
return _c
}
// Flush provides a mock function with given fields: _a0, _a1
func (_m *MilvusServiceServer) Flush(_a0 context.Context, _a1 *milvuspb.FlushRequest) (*milvuspb.FlushResponse, error) {
ret := _m.Called(_a0, _a1)
@@ -6787,6 +6902,65 @@ func (_c *MilvusServiceServer_ReplicateMessage_Call) RunAndReturn(run func(conte
return _c
}
// RestoreExternalSnapshot provides a mock function with given fields: _a0, _a1
func (_m *MilvusServiceServer) RestoreExternalSnapshot(_a0 context.Context, _a1 *milvuspb.RestoreExternalSnapshotRequest) (*milvuspb.RestoreExternalSnapshotResponse, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for RestoreExternalSnapshot")
}
var r0 *milvuspb.RestoreExternalSnapshotResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.RestoreExternalSnapshotRequest) (*milvuspb.RestoreExternalSnapshotResponse, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.RestoreExternalSnapshotRequest) *milvuspb.RestoreExternalSnapshotResponse); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*milvuspb.RestoreExternalSnapshotResponse)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.RestoreExternalSnapshotRequest) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MilvusServiceServer_RestoreExternalSnapshot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RestoreExternalSnapshot'
type MilvusServiceServer_RestoreExternalSnapshot_Call struct {
*mock.Call
}
// RestoreExternalSnapshot is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *milvuspb.RestoreExternalSnapshotRequest
func (_e *MilvusServiceServer_Expecter) RestoreExternalSnapshot(_a0 interface{}, _a1 interface{}) *MilvusServiceServer_RestoreExternalSnapshot_Call {
return &MilvusServiceServer_RestoreExternalSnapshot_Call{Call: _e.mock.On("RestoreExternalSnapshot", _a0, _a1)}
}
func (_c *MilvusServiceServer_RestoreExternalSnapshot_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.RestoreExternalSnapshotRequest)) *MilvusServiceServer_RestoreExternalSnapshot_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*milvuspb.RestoreExternalSnapshotRequest))
})
return _c
}
func (_c *MilvusServiceServer_RestoreExternalSnapshot_Call) Return(_a0 *milvuspb.RestoreExternalSnapshotResponse, _a1 error) *MilvusServiceServer_RestoreExternalSnapshot_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MilvusServiceServer_RestoreExternalSnapshot_Call) RunAndReturn(run func(context.Context, *milvuspb.RestoreExternalSnapshotRequest) (*milvuspb.RestoreExternalSnapshotResponse, error)) *MilvusServiceServer_RestoreExternalSnapshot_Call {
_c.Call.Return(run)
return _c
}
// RestoreRBAC provides a mock function with given fields: _a0, _a1
func (_m *MilvusServiceServer) RestoreRBAC(_a0 context.Context, _a1 *milvuspb.RestoreRBACMetaRequest) (*commonpb.Status, error) {
ret := _m.Called(_a0, _a1)
+3 -2
View File
@@ -53,8 +53,9 @@ func (c *Client) DescribeUser(ctx context.Context, opt DescribeUserOption, callO
}
result := resp.GetResults()[0]
user = &entity.User{
UserName: result.GetUser().GetName(),
Roles: lo.Map(result.GetRoles(), func(r *milvuspb.RoleEntity, _ int) string { return r.GetName() }),
UserName: result.GetUser().GetName(),
Roles: lo.Map(result.GetRoles(), func(r *milvuspb.RoleEntity, _ int) string { return r.GetName() }),
Description: result.GetDescription(),
}
return nil
+18 -4
View File
@@ -67,14 +67,16 @@ type CreateUserOption interface {
}
type createUserOption struct {
userName string
password string
userName string
password string
description *string
}
func (opt *createUserOption) Request() *milvuspb.CreateCredentialRequest {
return &milvuspb.CreateCredentialRequest{
Username: opt.userName,
Password: crypto.Base64Encode(opt.password),
Username: opt.userName,
Password: crypto.Base64Encode(opt.password),
Description: opt.description,
}
}
@@ -85,6 +87,11 @@ func NewCreateUserOption(userName, password string) *createUserOption {
}
}
func (opt *createUserOption) WithDescription(description string) *createUserOption {
opt.description = &description
return opt
}
type UpdatePasswordOption interface {
Request() *milvuspb.UpdateCredentialRequest
}
@@ -93,6 +100,7 @@ type updatePasswordOption struct {
userName string
oldPassword string
newPassword string
description *string
}
func (opt *updatePasswordOption) Request() *milvuspb.UpdateCredentialRequest {
@@ -100,6 +108,7 @@ func (opt *updatePasswordOption) Request() *milvuspb.UpdateCredentialRequest {
Username: opt.userName,
OldPassword: crypto.Base64Encode(opt.oldPassword),
NewPassword: crypto.Base64Encode(opt.newPassword),
Description: opt.description,
}
}
@@ -111,6 +120,11 @@ func NewUpdatePasswordOption(userName, oldPassword, newPassword string) *updateP
}
}
func (opt *updatePasswordOption) WithDescription(description string) *updatePasswordOption {
opt.description = &description
return opt
}
type DropUserOption interface {
Request() *milvuspb.DeleteCredentialRequest
}
+25 -3
View File
@@ -65,12 +65,14 @@ func (s *UserSuite) TestDescribeUser() {
userName := fmt.Sprintf("user_%s", s.randString(5))
s.Run("success", func() {
description := "go sdk user description"
s.mock.EXPECT().SelectUser(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, r *milvuspb.SelectUserRequest) (*milvuspb.SelectUserResponse, error) {
s.Equal(userName, r.GetUser().GetName())
return &milvuspb.SelectUserResponse{
Results: []*milvuspb.UserResult{
{
User: &milvuspb.UserEntity{Name: userName},
User: &milvuspb.UserEntity{Name: userName},
Description: description,
Roles: []*milvuspb.RoleEntity{
{Name: "role1"},
{Name: "role2"},
@@ -84,6 +86,7 @@ func (s *UserSuite) TestDescribeUser() {
s.NoError(err)
s.Equal(userName, user.UserName)
s.Equal([]string{"role1", "role2"}, user.Roles)
s.Equal(description, user.Description)
})
s.Run("failure", func() {
@@ -101,13 +104,15 @@ func (s *UserSuite) TestCreateUser() {
s.Run("success", func() {
userName := fmt.Sprintf("user_%s", s.randString(5))
password := s.randString(12)
description := "go sdk create description"
s.mock.EXPECT().CreateCredential(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, ccr *milvuspb.CreateCredentialRequest) (*commonpb.Status, error) {
s.Equal(userName, ccr.GetUsername())
s.Equal(crypto.Base64Encode(password), ccr.GetPassword())
s.Equal(description, ccr.GetDescription())
return merr.Success(), nil
}).Once()
err := s.client.CreateUser(ctx, NewCreateUserOption(userName, password))
err := s.client.CreateUser(ctx, NewCreateUserOption(userName, password).WithDescription(description))
s.NoError(err)
})
}
@@ -120,14 +125,31 @@ func (s *UserSuite) TestUpdatePassword() {
userName := fmt.Sprintf("user_%s", s.randString(5))
oldPassword := s.randString(12)
newPassword := s.randString(12)
description := "go sdk update description"
s.mock.EXPECT().UpdateCredential(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, ucr *milvuspb.UpdateCredentialRequest) (*commonpb.Status, error) {
s.Equal(userName, ucr.GetUsername())
s.Equal(crypto.Base64Encode(oldPassword), ucr.GetOldPassword())
s.Equal(crypto.Base64Encode(newPassword), ucr.GetNewPassword())
s.Equal(description, ucr.GetDescription())
return merr.Success(), nil
}).Once()
err := s.client.UpdatePassword(ctx, NewUpdatePasswordOption(userName, oldPassword, newPassword))
err := s.client.UpdatePassword(ctx, NewUpdatePasswordOption(userName, oldPassword, newPassword).WithDescription(description))
s.NoError(err)
})
s.Run("description only", func() {
userName := fmt.Sprintf("user_%s", s.randString(5))
description := "go sdk description only update"
s.mock.EXPECT().UpdateCredential(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, ucr *milvuspb.UpdateCredentialRequest) (*commonpb.Status, error) {
s.Equal(userName, ucr.GetUsername())
s.Empty(ucr.GetOldPassword())
s.Empty(ucr.GetNewPassword())
s.Equal(description, ucr.GetDescription())
return merr.Success(), nil
}).Once()
err := s.client.UpdatePassword(ctx, NewUpdatePasswordOption(userName, "", "").WithDescription(description))
s.NoError(err)
})
@@ -0,0 +1,173 @@
# MEP: RBAC User Description
- **Created:** 2026-06-01
- **Author(s):** @shaoting-huang
- **Status:** Under Review
- **Component:** Proxy | Coordinator
- **Related Issues:** #50179
- **Released:** Milvus release version, if applicable
## Summary
Milvus RBAC users can carry an optional human-readable description. The field is
accepted on user creation, returned by user read APIs, and can be edited through
the existing credential update API without requiring a password change.
**GitHub Issue**: https://github.com/milvus-io/milvus/issues/50179
## Motivation
RBAC users are currently identified only by username and role bindings. Operators
need a lightweight place to record who owns a user, what integration it belongs
to, or why it exists. The description must be editable without rotating the
password, and password rotation must not erase the description.
## Goals
- Persist a user description together with credential metadata.
- Return the description from user describe and select flows, including when
role information is not requested.
- Allow description-only updates through `UpdateCredential`.
- Preserve the existing password when only the description changes.
- Preserve the existing description when only the password changes.
- Avoid invalidating or blanking proxy authentication cache entries during
description-only updates.
## Non-Goals
- Add a new RPC for user description edits.
- Change RBAC authorization semantics. A caller with `PrivilegeUpdateUser` can
update a description without knowing the target user's password.
- Add user descriptions to RBAC backup and restore. The current proto dependency
does not add description to `UserInfo`.
## Public Interfaces
The milvus-proto dependency adds optional description fields to the existing
credential requests and user read result:
```protobuf
message CreateCredentialRequest {
optional string description = 6;
}
message UpdateCredentialRequest {
optional string description = 7;
}
message UserResult {
UserEntity user = 1;
repeated RoleEntity roles = 2;
string description = 3;
}
```
Milvus internal credential messages add the same optional field so the WAL body
can distinguish "field not provided" from "set description to empty string":
```protobuf
message CredentialInfo {
string username = 1;
string encrypted_password = 2;
string sha256_password = 5;
uint64 time_tick = 6;
optional string description = 7;
}
```
`proxy.maxUserDescriptionLength` limits the byte length of the description. The
default is 1024 bytes.
## Design Details
### Data Flow
#### Create User
1. Proxy validates username, password, and description length.
2. Proxy encrypts the password, computes the SHA256 cache value, and sends
`CredentialInfo` with `description` to RootCoord.
3. RootCoord broadcasts an alter-user WAL message.
4. The WAL ack callback writes the credential metadata and updates proxy auth
caches because a password is present.
#### Update Password
1. Proxy enters the password update path only when `new_password` is provided.
2. Proxy decodes and validates the old and new passwords, verifies the old
password unless the caller is a configured super user, then sends the new
encrypted password and SHA256 cache value.
3. RootCoord performs a read-modify-write merge. If the incoming message does
not carry `description`, the existing description is preserved.
4. The WAL ack callback updates proxy auth caches because the body carries a
non-empty SHA256 password.
#### Update Description Only
1. Proxy validates description length and skips the password block because
`new_password` is absent.
2. Proxy sends `CredentialInfo` with `description` and no password fields.
3. RootCoord merges the incoming metadata with the existing credential. Because
the incoming encrypted password is empty, the existing encrypted password is
preserved.
4. The WAL ack callback skips proxy auth cache updates because no SHA256
password is present.
#### Read User
`Catalog.getUserResult` loads the credential before the role-info early return
and copies `Credential.Description` into `UserResult.Description`. This makes
both `include_role_info=true` and `include_role_info=false` return the field.
## Storage Model
`model.Credential` stores `Description` in the same JSON payload as the encrypted
password and timetick. `Sha256Password` remains cache-only and is not persisted
by the RootCoord merge path.
Proxy enforces the description length before the request reaches RootCoord, so an
oversized description is rejected before it can increase the etcd credential
value. RootCoord keeps the existing credential validation shape and does not
duplicate proxy-side username, password, or description length checks.
HTTP v2 user create and update requests pass the optional description through to
the same gRPC credential APIs. HTTP v2 user describe preserves the existing role
list response and includes the description in the response object.
## Compatibility, Deprecation, and Migration Plan
The description field is optional. Existing credential records unmarshal with an
empty description. Existing clients that do not send descriptions continue to
create and update credentials as before.
This Milvus PR temporarily uses a git-based `replace` for the milvus-proto
branch that defines the API fields. During coordinated landing, the replace is
removed and the dependency is switched to the upstream milvus-proto version that
contains those fields.
No deprecation or data migration is required.
## Test Plan
- Proxy unit tests cover create with description, description length rejection,
description-only update, password-plus-description update, empty update
rejection, and empty-string description clearing.
- RootCoord unit tests cover read-modify-write preservation and proxy auth cache
behavior, including malformed password updates.
- Metastore tests cover credential marshal/unmarshal and user result readback.
- HTTP v2 tests cover create, description-only update, and describe response
propagation.
- CI runs package tests, static check, and Go-only builds against the coordinated
milvus-proto dependency.
## Rejected Alternatives
- Add a new update-description RPC. Rejected because the existing
`UpdateCredential` API already owns credential metadata updates and can model
password and description as independently optional fields.
- Store descriptions in a separate metadata key. Rejected because credentials
already have a versioned WAL update path and catalog record. A separate key
would add another consistency edge without reducing update complexity.
- Update proxy auth caches on every credential metadata edit. Rejected because a
description-only update carries no SHA256 password and would blank cached
authentication state.
+1 -2
View File
@@ -69,8 +69,7 @@ require (
github.com/hamba/avro/v2 v2.29.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/magiconair/properties v1.8.7
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.18
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260526093827-489331a5a41f
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260612100615-43795e8f8f6e
github.com/milvus-io/milvus/client/v2 v2.6.2
github.com/milvus-io/milvus/pkg/v3 v3.0.0-beta
github.com/shirou/gopsutil/v4 v4.25.10
+2 -4
View File
@@ -795,10 +795,8 @@ github.com/milvus-io/cgosymbolizer v0.0.0-20250318084424-114f4050c3a6 h1:YHMFI6L
github.com/milvus-io/cgosymbolizer v0.0.0-20250318084424-114f4050c3a6/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg=
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b h1:TfeY0NxYxZzUfIfYe5qYDBzt4ZYRqzUjTR6CvUzjat8=
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b/go.mod h1:iwW+9cWfIzzDseEBCCeDSN5SD16Tidvy8cwQ7ZY8Qj4=
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.18 h1:4EaGTuDn0yv9BtQ23l2Wube7d8XcLA4+z2sQsgRkYK0=
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.18/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260526093827-489331a5a41f h1:W+paUzc+AA381Rydx2DJffoQuM4l+efVqO+2i9GdfMw=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260526093827-489331a5a41f/go.mod h1:rbKpv5JToISTKTTLl0duL5r6wbYnjJ9SsD0QgXMzKy0=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260612100615-43795e8f8f6e h1:axPnOhjsWqqxk0dOQRSIU2mdg52Kyl/8E6iDFZy/IpE=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260612100615-43795e8f8f6e/go.mod h1:rbKpv5JToISTKTTLl0duL5r6wbYnjJ9SsD0QgXMzKy0=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=
@@ -2663,14 +2663,18 @@ func (h *HandlersV2) describeUser(ctx context.Context, c *gin.Context, anyReq an
})
if err == nil {
roleNames := []string{}
description := ""
for _, userRole := range resp.(*milvuspb.SelectUserResponse).Results {
if userRole.User.Name == userName {
description = userRole.GetDescription()
for _, role := range userRole.Roles {
roleNames = append(roleNames, role.Name)
}
}
}
HTTPReturn(c, http.StatusOK, wrapperReturnList(roleNames))
result := wrapperReturnList(roleNames)
result[HTTPReturnDescription] = description
HTTPReturn(c, http.StatusOK, result)
}
return resp, err
}
@@ -2678,8 +2682,9 @@ func (h *HandlersV2) describeUser(ctx context.Context, c *gin.Context, anyReq an
func (h *HandlersV2) createUser(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*PasswordReq)
req := &milvuspb.CreateCredentialRequest{
Username: httpReq.UserName,
Password: crypto.Base64Encode(httpReq.Password),
Username: httpReq.UserName,
Password: crypto.Base64Encode(httpReq.Password),
Description: httpReq.Description,
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/CreateCredential", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.CreateCredential(reqCtx, req.(*milvuspb.CreateCredentialRequest))
@@ -2694,8 +2699,11 @@ func (h *HandlersV2) updateUser(ctx context.Context, c *gin.Context, anyReq any,
httpReq := anyReq.(*NewPasswordReq)
req := &milvuspb.UpdateCredentialRequest{
Username: httpReq.UserName,
OldPassword: crypto.Base64Encode(httpReq.Password),
NewPassword: crypto.Base64Encode(httpReq.NewPassword),
Description: httpReq.Description,
}
if httpReq.NewPassword != "" {
req.OldPassword = crypto.Base64Encode(httpReq.Password)
req.NewPassword = crypto.Base64Encode(httpReq.NewPassword)
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/UpdateCredential", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.UpdateCredential(reqCtx, req.(*milvuspb.UpdateCredentialRequest))
@@ -29,6 +29,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"google.golang.org/protobuf/proto"
@@ -44,6 +45,7 @@ import (
"github.com/milvus-io/milvus/pkg/v3/common"
"github.com/milvus-io/milvus/pkg/v3/proto/internalpb"
"github.com/milvus-io/milvus/pkg/v3/util"
"github.com/milvus-io/milvus/pkg/v3/util/crypto"
"github.com/milvus-io/milvus/pkg/v3/util/merr"
"github.com/milvus-io/milvus/pkg/v3/util/paramtable"
)
@@ -2879,6 +2881,79 @@ func TestMethodPost(t *testing.T) {
}
}
func TestUserDescriptionV2(t *testing.T) {
paramtable.Init()
description := "用户描述"
mp := mocks.NewMockProxy(t)
mp.EXPECT().
CreateCredential(mock.Anything, mock.MatchedBy(func(req *milvuspb.CreateCredentialRequest) bool {
return req.GetUsername() == util.UserRoot &&
req.GetPassword() == crypto.Base64Encode("Milvus") &&
req.Description != nil &&
req.GetDescription() == description
})).
Return(commonSuccessStatus, nil).
Once()
mp.EXPECT().
UpdateCredential(mock.Anything, mock.MatchedBy(func(req *milvuspb.UpdateCredentialRequest) bool {
return req.GetUsername() == util.UserRoot &&
req.GetOldPassword() == "" &&
req.GetNewPassword() == "" &&
req.Description != nil &&
req.GetDescription() == description
})).
Return(commonSuccessStatus, nil).
Once()
mp.EXPECT().
SelectUser(mock.Anything, mock.Anything).
Return(&milvuspb.SelectUserResponse{
Status: &StatusSuccess,
Results: []*milvuspb.UserResult{
{
User: &milvuspb.UserEntity{Name: util.UserRoot},
Roles: []*milvuspb.RoleEntity{{Name: util.RoleAdmin}},
Description: description,
},
},
}, nil).
Once()
testEngine := initHTTPServerV2(mp, false)
for _, testcase := range []struct {
path string
body string
}{
{
path: versionalV2(UserCategory, CreateAction),
body: `{"userName":"` + util.UserRoot + `","password":"Milvus","description":"` + description + `"}`,
},
{
path: versionalV2(UserCategory, UpdatePasswordAction),
body: `{"userName":"` + util.UserRoot + `","description":"` + description + `"}`,
},
{
path: versionalV2(UserCategory, DescribeAction),
body: `{"userName":"` + util.UserRoot + `"}`,
},
} {
t.Run(testcase.path, func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, testcase.path, bytes.NewReader([]byte(testcase.body)))
w := httptest.NewRecorder()
testEngine.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
resp := map[string]interface{}{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
assert.Equal(t, float64(0), resp[HTTPReturnCode])
if testcase.path == versionalV2(UserCategory, DescribeAction) {
assert.Equal(t, description, resp[HTTPReturnDescription])
assert.Equal(t, []interface{}{util.RoleAdmin}, resp[HTTPReturnData])
}
})
}
}
func validateTestCases(t *testing.T, testEngine *gin.Engine, queryTestCases []requestBodyTestCase, allowInt64 bool) {
for i, testcase := range queryTestCases {
t.Run(testcase.path, func(t *testing.T) {
@@ -519,14 +519,16 @@ type TimestampGetter interface {
GetTimestamp() uint64
}
type PasswordReq struct {
UserName string `json:"userName" binding:"required"`
Password string `json:"password" binding:"required"`
UserName string `json:"userName" binding:"required"`
Password string `json:"password" binding:"required"`
Description *string `json:"description"`
}
type NewPasswordReq struct {
UserName string `json:"userName" binding:"required"`
Password string `json:"password" binding:"required"`
NewPassword string `json:"newPassword" binding:"required"`
UserName string `json:"userName" binding:"required"`
Password string `json:"password"`
NewPassword string `json:"newPassword"`
Description *string `json:"description"`
}
type UserRoleReq struct {
-1
View File
@@ -90,7 +90,6 @@ type RootCoordCatalog interface {
// MigrateGrantCollectionName migrates all grants from oldName to newName when a collection is renamed.
MigrateGrantCollectionName(ctx context.Context, tenant string, oldDBName string, oldName string, newDBName string, newName string) error
ListCredentialsWithPasswd(ctx context.Context) (map[string]string, error)
BackupRBAC(ctx context.Context, tenant string) (*milvuspb.RBACMeta, error)
RestoreRBAC(ctx context.Context, tenant string, meta *milvuspb.RBACMeta) error
+63 -51
View File
@@ -360,7 +360,8 @@ func (kc *Catalog) CreateAlias(ctx context.Context, alias *model.Alias, ts typeu
func (kc *Catalog) AlterCredential(ctx context.Context, credential *model.Credential) error {
k := fmt.Sprintf("%s/%s", CredentialPrefix, credential.Username)
credentialInfo := model.MarshalCredentialModel(credential)
credentialInfo.Username = "" // Username is already save in the key, remove it from the value.
credentialInfo.Username = "" // Username is already save in the key, remove it from the value.
credentialInfo.Sha256Password = "" // Sha256Password is cache-only, do not persist it.
v, err := json.Marshal(credentialInfo)
if err != nil {
log.Ctx(ctx).Error("create credential marshal fail", zap.String("key", k), zap.Error(err))
@@ -1129,37 +1130,45 @@ func (kc *Catalog) ListAliases(ctx context.Context, dbID int64, ts typeutil.Time
}
func (kc *Catalog) ListCredentials(ctx context.Context) ([]string, error) {
users, err := kc.ListCredentialsWithPasswd(ctx)
credentials, err := kc.listCredentials(ctx)
if err != nil {
return nil, err
}
return lo.Keys(users), nil
return lo.Map(credentials, func(credential *model.Credential, _ int) string {
return credential.Username
}), nil
}
func (kc *Catalog) ListCredentialsWithPasswd(ctx context.Context) (map[string]string, error) {
func (kc *Catalog) listCredentials(ctx context.Context) ([]*model.Credential, error) {
keys, values, err := kc.Txn.LoadWithPrefix(ctx, CredentialPrefix+"/")
if err != nil {
log.Ctx(ctx).Error("list all credential usernames fail", zap.String("prefix", CredentialPrefix), zap.Error(err))
log.Ctx(ctx).Error("list all credentials fail", zap.String("prefix", CredentialPrefix), zap.Error(err))
return nil, err
}
users := make(map[string]string)
credentials := make([]*model.Credential, 0, len(keys))
prefix := CredentialPrefix + "/"
for i := range keys {
username := typeutil.After(keys[i], UserSubPrefix+"/")
if len(username) == 0 {
log.Ctx(ctx).Warn("no username extract from path:", zap.String("path", keys[i]))
prefixPos := strings.Index(keys[i], prefix)
if prefixPos < 0 {
log.Ctx(ctx).Warn("invalid credential key", zap.String("path", keys[i]), zap.String("prefix", prefix))
continue
}
credential := &internalpb.CredentialInfo{}
err := json.Unmarshal([]byte(values[i]), credential)
if err != nil {
username := keys[i][prefixPos+len(prefix):]
if len(username) == 0 || strings.Contains(username, "/") {
log.Ctx(ctx).Warn("invalid credential key", zap.String("path", keys[i]), zap.String("prefix", prefix))
continue
}
credentialInfo := &internalpb.CredentialInfo{}
if err := json.Unmarshal([]byte(values[i]), credentialInfo); err != nil {
log.Ctx(ctx).Error("credential unmarshal fail", zap.String("key", keys[i]), zap.Error(err))
return nil, err
}
users[username] = credential.EncryptedPassword
credentialInfo.Username = username
credentials = append(credentials, model.UnmarshalCredentialModel(credentialInfo))
}
return users, nil
return credentials, nil
}
func (kc *Catalog) remove(ctx context.Context, k string) error {
@@ -1302,13 +1311,17 @@ func (kc *Catalog) getRolesByUsername(ctx context.Context, tenant string, userna
return roles, nil
}
// getUserResult get the user result by the username. And never return the error because the error means the user isn't added to a role.
func (kc *Catalog) getUserResult(ctx context.Context, tenant string, username string, includeRoleInfo bool) (*milvuspb.UserResult, error) {
result := &milvuspb.UserResult{User: &milvuspb.UserEntity{Name: username}}
// getUserResult gets the user result from a loaded credential.
func (kc *Catalog) getUserResult(ctx context.Context, tenant string, credential *model.Credential, includeRoleInfo bool) (*milvuspb.UserResult, error) {
result := &milvuspb.UserResult{
User: &milvuspb.UserEntity{Name: credential.Username},
Description: credential.Description,
}
if !includeRoleInfo {
return result, nil
}
roleNames, err := kc.getRolesByUsername(ctx, tenant, username)
roleNames, err := kc.getRolesByUsername(ctx, tenant, credential.Username)
if err != nil {
log.Ctx(ctx).Warn("fail to get roles by the username", zap.Error(err))
return result, err
@@ -1323,40 +1336,33 @@ func (kc *Catalog) getUserResult(ctx context.Context, tenant string, username st
func (kc *Catalog) ListUser(ctx context.Context, tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error) {
var (
usernames []string
err error
results []*milvuspb.UserResult
credentials []*model.Credential
err error
)
appendUserResult := func(username string) error {
result, err := kc.getUserResult(ctx, tenant, username, includeRoleInfo)
if err != nil {
return err
}
results = append(results, result)
return nil
}
if entity == nil {
usernames, err = kc.ListCredentials(ctx)
if err != nil {
return results, err
}
} else {
if funcutil.IsEmptyString(entity.Name) {
return results, merr.WrapErrParameterInvalidMsg("username in the user entity is empty")
}
_, err = kc.GetCredential(ctx, entity.Name)
if err != nil {
return results, err
}
usernames = append(usernames, entity.Name)
}
for _, username := range usernames {
err = appendUserResult(username)
credentials, err = kc.listCredentials(ctx)
if err != nil {
return nil, err
}
} else {
if funcutil.IsEmptyString(entity.Name) {
return nil, merr.WrapErrParameterInvalidMsg("username in the user entity is empty")
}
credential, err := kc.GetCredential(ctx, entity.Name)
if err != nil {
return nil, err
}
credentials = []*model.Credential{credential}
}
results := make([]*milvuspb.UserResult, 0, len(credentials))
for _, credential := range credentials {
result, err := kc.getUserResult(ctx, tenant, credential, includeRoleInfo)
if err != nil {
return nil, err
}
results = append(results, result)
}
return results, nil
}
@@ -1987,14 +1993,20 @@ func (kc *Catalog) ListUserRole(ctx context.Context, tenant string) ([]string, e
}
func (kc *Catalog) BackupRBAC(ctx context.Context, tenant string) (*milvuspb.RBACMeta, error) {
users, err := kc.ListUser(ctx, tenant, nil, true)
credentials, err := kc.listCredentials(ctx)
if err != nil {
return nil, err
}
credentials, err := kc.ListCredentialsWithPasswd(ctx)
if err != nil {
return nil, err
users := make([]*milvuspb.UserResult, 0, len(credentials))
credentialPasswords := make(map[string]string, len(credentials))
for _, credential := range credentials {
credentialPasswords[credential.Username] = credential.EncryptedPassword
user, err := kc.getUserResult(ctx, tenant, credential, true)
if err != nil {
return nil, err
}
users = append(users, user)
}
userInfos := lo.FilterMap(users, func(entity *milvuspb.UserResult, _ int) (*milvuspb.UserInfo, bool) {
@@ -2004,7 +2016,7 @@ func (kc *Catalog) BackupRBAC(ctx context.Context, tenant string) (*milvuspb.RBA
}
return &milvuspb.UserInfo{
User: userName,
Password: credentials[userName],
Password: credentialPasswords[userName],
Roles: entity.GetRoles(),
}, true
})
@@ -1607,6 +1607,133 @@ func getUserInfoMetaString(username string) string {
return string(validBytes)
}
func TestGetUserResultIncludesDescriptionWithoutRoleInfo(t *testing.T) {
ctx := context.TODO()
tenant := util.DefaultTenant
username := "user-with-description"
description := "stored description"
kvmock := mocks.NewTxnKV(t)
c := NewCatalog(kvmock).(*Catalog)
credential := &model.Credential{
Username: username,
Description: description,
}
result, err := c.getUserResult(ctx, tenant, credential, false)
require.NoError(t, err)
assert.Equal(t, username, result.GetUser().GetName())
assert.Equal(t, description, result.GetDescription())
assert.Empty(t, result.GetRoles())
}
func TestListUserReturnsCredentialLoadError(t *testing.T) {
ctx := context.TODO()
tenant := util.DefaultTenant
username := "user-with-credential-load-error"
credentialKey := fmt.Sprintf("%s/%s", CredentialPrefix, username)
expectedErr := errors.New("credential load failed")
kvmock := mocks.NewTxnKV(t)
c := NewCatalog(kvmock).(*Catalog)
kvmock.EXPECT().Load(mock.Anything, credentialKey).Return("", expectedErr)
results, err := c.ListUser(ctx, tenant, &milvuspb.UserEntity{Name: username}, false)
require.ErrorIs(t, err, expectedErr)
assert.Empty(t, results)
}
func TestListUserSingleUserReusesLoadedCredentialDescription(t *testing.T) {
ctx := context.TODO()
tenant := util.DefaultTenant
username := "user-with-description"
description := "single user description"
credentialKey := fmt.Sprintf("%s/%s", CredentialPrefix, username)
credentialValue, err := json.Marshal(&internalpb.CredentialInfo{Description: &description})
require.NoError(t, err)
kvmock := mocks.NewTxnKV(t)
c := NewCatalog(kvmock).(*Catalog)
kvmock.EXPECT().Load(mock.Anything, credentialKey).Return(string(credentialValue), nil).Once()
results, err := c.ListUser(ctx, tenant, &milvuspb.UserEntity{Name: username}, false)
require.NoError(t, err)
require.Len(t, results, 1)
assert.Equal(t, username, results[0].GetUser().GetName())
assert.Equal(t, description, results[0].GetDescription())
assert.Empty(t, results[0].GetRoles())
}
func TestListUserAllUsersReusesLoadedCredentialDescriptions(t *testing.T) {
ctx := context.TODO()
tenant := util.DefaultTenant
user1Description := "first user"
user2Description := "second user"
ghostDescription := "ghost user"
user1Credential, err := json.Marshal(&internalpb.CredentialInfo{Description: &user1Description})
require.NoError(t, err)
user2Credential, err := json.Marshal(&internalpb.CredentialInfo{Description: &user2Description})
require.NoError(t, err)
ghostCredential, err := json.Marshal(&internalpb.CredentialInfo{Description: &ghostDescription})
require.NoError(t, err)
kvmock := mocks.NewTxnKV(t)
c := NewCatalog(kvmock).(*Catalog)
kvmock.EXPECT().LoadWithPrefix(mock.Anything, CredentialPrefix+"/").Return(
[]string{
fmt.Sprintf("%s/%s", CredentialPrefix, "user1"),
fmt.Sprintf("%s/%s", CredentialPrefix, "user2"),
fmt.Sprintf("%s/%s/%s", CredentialPrefix, UserSubPrefix, "ghost"),
},
[]string{string(user1Credential), string(user2Credential), string(ghostCredential)},
nil,
)
results, err := c.ListUser(ctx, tenant, nil, false)
require.NoError(t, err)
require.Len(t, results, 2)
assert.Equal(t, "user1", results[0].GetUser().GetName())
assert.Equal(t, user1Description, results[0].GetDescription())
assert.Empty(t, results[0].GetRoles())
assert.Equal(t, "user2", results[1].GetUser().GetName())
assert.Equal(t, user2Description, results[1].GetDescription())
assert.Empty(t, results[1].GetRoles())
}
func TestBackupRBACReusesLoadedCredentials(t *testing.T) {
ctx := context.TODO()
tenant := util.DefaultTenant
username := "user-with-password"
password := "encrypted-password"
credentialValue, err := json.Marshal(&internalpb.CredentialInfo{EncryptedPassword: password})
require.NoError(t, err)
var credentialPrefixLoads int
kvmock := mocks.NewTxnKV(t)
c := NewCatalog(kvmock).(*Catalog)
kvmock.EXPECT().LoadWithPrefix(mock.Anything, mock.Anything).RunAndReturn(
func(ctx context.Context, key string) ([]string, []string, error) {
if key == CredentialPrefix+"/" {
credentialPrefixLoads++
return []string{fmt.Sprintf("%s/%s", CredentialPrefix, username)}, []string{string(credentialValue)}, nil
}
return nil, nil, nil
})
backup, err := c.BackupRBAC(ctx, tenant)
require.NoError(t, err)
assert.Equal(t, 1, credentialPrefixLoads)
require.Len(t, backup.GetUsers(), 1)
assert.Equal(t, username, backup.GetUsers()[0].GetUser())
assert.Equal(t, password, backup.GetUsers()[0].GetPassword())
assert.Empty(t, backup.GetUsers()[0].GetRoles())
}
func TestRBAC_Credential(t *testing.T) {
ctx := context.TODO()
@@ -1711,6 +1838,32 @@ func TestRBAC_Credential(t *testing.T) {
}
})
t.Run("test AlterCredential does not persist sha256 password", func(t *testing.T) {
var (
kvmock = mocks.NewTxnKV(t)
c = NewCatalog(kvmock)
)
kvmock.EXPECT().
Save(mock.Anything, fmt.Sprintf("%s/%s", CredentialPrefix, "user1"), mock.MatchedBy(func(value string) bool {
credential := &internalpb.CredentialInfo{}
require.NoError(t, json.Unmarshal([]byte(value), credential))
return credential.GetEncryptedPassword() == "password" &&
credential.GetSha256Password() == "" &&
credential.GetDescription() == "description"
})).
Return(nil)
description := "description"
err := c.AlterCredential(ctx, &model.Credential{
Username: "user1",
EncryptedPassword: "password",
Sha256Password: "sha256-password",
Description: description,
})
assert.NoError(t, err)
})
t.Run("test DropCredential", func(t *testing.T) {
var (
kvmock = mocks.NewTxnKV(t)
@@ -1780,6 +1933,8 @@ func TestRBAC_Credential(t *testing.T) {
// Return valid keys if count==0
// return error if count!=0
credentialValue, err := json.Marshal(&internalpb.CredentialInfo{EncryptedPassword: crypto.Base64Encode("passwd")})
require.NoError(t, err)
kvmock.EXPECT().LoadWithPrefix(mock.Anything, mock.Anything).Call.Return(
func(ctx context.Context, key string) []string {
cmu.RLock()
@@ -1789,6 +1944,7 @@ func TestRBAC_Credential(t *testing.T) {
fmt.Sprintf("%s/%s", CredentialPrefix, "user1"),
fmt.Sprintf("%s/%s", CredentialPrefix, "user2"),
fmt.Sprintf("%s/%s", CredentialPrefix, "user3"),
fmt.Sprintf("%s/%s/%s", CredentialPrefix, UserSubPrefix, "ghost"),
"random",
}
}
@@ -1797,13 +1953,13 @@ func TestRBAC_Credential(t *testing.T) {
func(ctx context.Context, key string) []string {
cmu.RLock()
defer cmu.RUnlock()
passwd, _ := json.Marshal(&model.Credential{EncryptedPassword: crypto.Base64Encode("passwd")})
if count == 0 {
return []string{
string(passwd),
string(passwd),
string(passwd),
string(passwd),
string(credentialValue),
string(credentialValue),
string(credentialValue),
string(credentialValue),
string(credentialValue),
}
}
return nil
@@ -2119,11 +2275,12 @@ func TestRBAC_Role(t *testing.T) {
kvmock = mocks.NewTxnKV(t)
c = NewCatalog(kvmock).(*Catalog)
invalidUser = "invalid-user"
invalidUserKey = funcutil.HandleTenantForEtcdPrefix(RoleMappingPrefix, tenant, invalidUser)
invalidUser = "invalid-user"
invalidRoleUser = "invalid-role-user"
invalidRoleUserKey = funcutil.HandleTenantForEtcdPrefix(RoleMappingPrefix, tenant, invalidRoleUser)
)
// returns error for invalidUserKey
kvmock.EXPECT().LoadWithPrefix(mock.Anything, invalidUserKey).Call.Return(
// returns error for invalidRoleUserKey
kvmock.EXPECT().LoadWithPrefix(mock.Anything, invalidRoleUserKey).Call.Return(
nil, nil, errors.New("Mock load with prefix wrong"))
// Returns keys for RoleMappingPrefix/tenant/user1/ (with trailing slash after the fix)
@@ -2136,6 +2293,9 @@ func TestRBAC_Role(t *testing.T) {
user1Key + "role3/error",
}
}, nil, nil)
kvmock.EXPECT().Load(mock.Anything, fmt.Sprintf("%s/%s", CredentialPrefix, "user1")).Return(getUserInfoMetaString("user1"), nil).Maybe()
kvmock.EXPECT().Load(mock.Anything, fmt.Sprintf("%s/%s", CredentialPrefix, invalidUser)).Return("", errors.New("mock load credential error")).Maybe()
kvmock.EXPECT().Load(mock.Anything, fmt.Sprintf("%s/%s", CredentialPrefix, invalidRoleUser)).Return(getUserInfoMetaString(invalidRoleUser), nil).Maybe()
// Returns keys for CredentialPrefix
var loadCredentialPrefixReturn atomic.Bool
@@ -2143,7 +2303,7 @@ func TestRBAC_Role(t *testing.T) {
func(ctx context.Context, key string) []string {
if loadCredentialPrefixReturn.Load() {
return []string{
fmt.Sprintf("%s/%s/%s", CredentialPrefix, UserSubPrefix, "user1"),
fmt.Sprintf("%s/%s", CredentialPrefix, "user1"),
}
}
return nil
@@ -2168,20 +2328,19 @@ func TestRBAC_Role(t *testing.T) {
description string
isValid bool
user string
credential *model.Credential
includeRoleInfo bool
}{
{"valid user1 not include RoleInfo", true, "user1", false},
{"valid user1 include RoleInfo", true, "user1", true},
{"invalid user not include RoleInfo", true, invalidUser, false},
{"invalid user include RoleInfo", false, invalidUser, true},
{"valid user1 not include RoleInfo", true, &model.Credential{Username: "user1"}, false},
{"valid user1 include RoleInfo", true, &model.Credential{Username: "user1"}, true},
{"invalid role user include RoleInfo", false, &model.Credential{Username: invalidRoleUser}, true},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
res, err := c.getUserResult(ctx, tenant, test.user, test.includeRoleInfo)
res, err := c.getUserResult(ctx, tenant, test.credential, test.includeRoleInfo)
assert.Equal(t, test.user, res.GetUser().GetName())
assert.Equal(t, test.credential.Username, res.GetUser().GetName())
if test.isValid {
assert.NoError(t, err)
@@ -2231,6 +2390,7 @@ func TestRBAC_Role(t *testing.T) {
{true, true, &milvuspb.UserEntity{Name: "user1"}, false, "valid entity user1 not include RoleInfo"},
{true, true, &milvuspb.UserEntity{Name: "user1"}, true, "valid entity user1 include RoleInfo"},
{false, true, &milvuspb.UserEntity{Name: invalidUser}, true, "invalid entity invalidUser include RoleInfo"},
{false, true, &milvuspb.UserEntity{Name: invalidRoleUser}, true, "invalid entity invalidRoleUser include RoleInfo"},
}
for _, test := range tests {
@@ -3282,10 +3442,11 @@ func TestRBAC_Restore(t *testing.T) {
assert.NoError(t, err)
// check user
users, err := c.ListCredentialsWithPasswd(ctx)
credentials, err := c.(*Catalog).listCredentials(ctx)
assert.NoError(t, err)
assert.Len(t, users, 1)
assert.Equal(t, users["user1"], "passwd")
assert.Len(t, credentials, 1)
assert.Equal(t, "user1", credentials[0].Username)
assert.Equal(t, "passwd", credentials[0].EncryptedPassword)
// check role
roles, err := c.ListRole(ctx, util.DefaultTenant, nil, false)
assert.NoError(t, err)
@@ -3353,9 +3514,9 @@ func TestRBAC_Restore(t *testing.T) {
assert.NoError(t, err)
// check user
users, err = c.ListCredentialsWithPasswd(ctx)
credentials, err = c.(*Catalog).listCredentials(ctx)
assert.NoError(t, err)
assert.Len(t, users, 2)
assert.Len(t, credentials, 2)
// check role
roles, err = c.ListRole(ctx, util.DefaultTenant, nil, false)
assert.NoError(t, err)
@@ -5,18 +5,13 @@ package mocks
import (
context "context"
msgpb "github.com/milvus-io/milvus-proto/go-api/v3/msgpb"
metastore "github.com/milvus-io/milvus/internal/metastore"
model "github.com/milvus-io/milvus/internal/metastore/model"
datapb "github.com/milvus-io/milvus/pkg/v3/proto/datapb"
indexpb "github.com/milvus-io/milvus/pkg/v3/proto/indexpb"
internalpb "github.com/milvus-io/milvus/pkg/v3/proto/internalpb"
metastore "github.com/milvus-io/milvus/internal/metastore"
mock "github.com/stretchr/testify/mock"
model "github.com/milvus-io/milvus/internal/metastore/model"
msgpb "github.com/milvus-io/milvus-proto/go-api/v3/msgpb"
)
// DataCoordCatalog is an autogenerated mock type for the DataCoordCatalog type
@@ -5,9 +5,8 @@ package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
querypb "github.com/milvus-io/milvus/pkg/v3/proto/querypb"
mock "github.com/stretchr/testify/mock"
)
// QueryCoordCatalog is an autogenerated mock type for the QueryCoordCatalog type
@@ -5,14 +5,11 @@ package mocks
import (
context "context"
metastore "github.com/milvus-io/milvus/internal/metastore"
internalpb "github.com/milvus-io/milvus/pkg/v3/proto/internalpb"
milvuspb "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb"
mock "github.com/stretchr/testify/mock"
metastore "github.com/milvus-io/milvus/internal/metastore"
model "github.com/milvus-io/milvus/internal/metastore/model"
internalpb "github.com/milvus-io/milvus/pkg/v3/proto/internalpb"
mock "github.com/stretchr/testify/mock"
)
// RootCoordCatalog is an autogenerated mock type for the RootCoordCatalog type
@@ -1655,64 +1652,6 @@ func (_c *RootCoordCatalog_ListCredentials_Call) RunAndReturn(run func(context.C
return _c
}
// ListCredentialsWithPasswd provides a mock function with given fields: ctx
func (_m *RootCoordCatalog) ListCredentialsWithPasswd(ctx context.Context) (map[string]string, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for ListCredentialsWithPasswd")
}
var r0 map[string]string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) (map[string]string, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) map[string]string); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]string)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RootCoordCatalog_ListCredentialsWithPasswd_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListCredentialsWithPasswd'
type RootCoordCatalog_ListCredentialsWithPasswd_Call struct {
*mock.Call
}
// ListCredentialsWithPasswd is a helper method to define mock.On call
// - ctx context.Context
func (_e *RootCoordCatalog_Expecter) ListCredentialsWithPasswd(ctx interface{}) *RootCoordCatalog_ListCredentialsWithPasswd_Call {
return &RootCoordCatalog_ListCredentialsWithPasswd_Call{Call: _e.mock.On("ListCredentialsWithPasswd", ctx)}
}
func (_c *RootCoordCatalog_ListCredentialsWithPasswd_Call) Run(run func(ctx context.Context)) *RootCoordCatalog_ListCredentialsWithPasswd_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *RootCoordCatalog_ListCredentialsWithPasswd_Call) Return(_a0 map[string]string, _a1 error) *RootCoordCatalog_ListCredentialsWithPasswd_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *RootCoordCatalog_ListCredentialsWithPasswd_Call) RunAndReturn(run func(context.Context) (map[string]string, error)) *RootCoordCatalog_ListCredentialsWithPasswd_Call {
_c.Call.Return(run)
return _c
}
// ListDatabases provides a mock function with given fields: ctx, ts
func (_m *RootCoordCatalog) ListDatabases(ctx context.Context, ts uint64) ([]*model.Database, error) {
ret := _m.Called(ctx, ts)
+3
View File
@@ -8,6 +8,7 @@ type Credential struct {
Tenant string
IsSuper bool
Sha256Password string
Description string
TimeTick uint64 // the timetick in wal which the credential updates
}
@@ -21,6 +22,7 @@ func MarshalCredentialModel(cred *Credential) *internalpb.CredentialInfo {
EncryptedPassword: cred.EncryptedPassword,
IsSuper: cred.IsSuper,
Sha256Password: cred.Sha256Password,
Description: &cred.Description,
TimeTick: cred.TimeTick,
}
}
@@ -35,6 +37,7 @@ func UnmarshalCredentialModel(cred *internalpb.CredentialInfo) *Credential {
Tenant: cred.Tenant,
IsSuper: cred.IsSuper,
Sha256Password: cred.Sha256Password,
Description: cred.GetDescription(),
TimeTick: cred.TimeTick,
}
}
@@ -15,14 +15,17 @@ var (
Tenant: "tenant-1",
IsSuper: true,
Sha256Password: "xxxx",
Description: "description",
}
description = "description"
credentialPb = &internalpb.CredentialInfo{
Username: "user",
EncryptedPassword: "password",
Tenant: "tenant-1",
IsSuper: true,
Sha256Password: "xxxx",
Description: &description,
}
)
+181 -10
View File
@@ -6,19 +6,13 @@ import (
context "context"
commonpb "github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
federpb "github.com/milvus-io/milvus-proto/go-api/v3/federpb"
internalpb "github.com/milvus-io/milvus/pkg/v3/proto/internalpb"
milvuspb "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb"
mock "github.com/stretchr/testify/mock"
datapb "github.com/milvus-io/milvus/pkg/v3/proto/datapb"
proxypb "github.com/milvus-io/milvus/pkg/v3/proto/proxypb"
types "github.com/milvus-io/milvus/internal/types"
datapb "github.com/milvus-io/milvus/pkg/v3/proto/datapb"
internalpb "github.com/milvus-io/milvus/pkg/v3/proto/internalpb"
proxypb "github.com/milvus-io/milvus/pkg/v3/proto/proxypb"
mock "github.com/stretchr/testify/mock"
)
// MockProxy is an autogenerated mock type for the ProxyComponent type
@@ -860,6 +854,65 @@ func (_c *MockProxy_AlterIndex_Call) RunAndReturn(run func(context.Context, *mil
return _c
}
// AlterRole provides a mock function with given fields: _a0, _a1
func (_m *MockProxy) AlterRole(_a0 context.Context, _a1 *milvuspb.AlterRoleRequest) (*commonpb.Status, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for AlterRole")
}
var r0 *commonpb.Status
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.AlterRoleRequest) (*commonpb.Status, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.AlterRoleRequest) *commonpb.Status); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*commonpb.Status)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.AlterRoleRequest) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockProxy_AlterRole_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AlterRole'
type MockProxy_AlterRole_Call struct {
*mock.Call
}
// AlterRole is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *milvuspb.AlterRoleRequest
func (_e *MockProxy_Expecter) AlterRole(_a0 interface{}, _a1 interface{}) *MockProxy_AlterRole_Call {
return &MockProxy_AlterRole_Call{Call: _e.mock.On("AlterRole", _a0, _a1)}
}
func (_c *MockProxy_AlterRole_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.AlterRoleRequest)) *MockProxy_AlterRole_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*milvuspb.AlterRoleRequest))
})
return _c
}
func (_c *MockProxy_AlterRole_Call) Return(_a0 *commonpb.Status, _a1 error) *MockProxy_AlterRole_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockProxy_AlterRole_Call) RunAndReturn(run func(context.Context, *milvuspb.AlterRoleRequest) (*commonpb.Status, error)) *MockProxy_AlterRole_Call {
_c.Call.Return(run)
return _c
}
// BackupRBAC provides a mock function with given fields: _a0, _a1
func (_m *MockProxy) BackupRBAC(_a0 context.Context, _a1 *milvuspb.BackupRBACMetaRequest) (*milvuspb.BackupRBACMetaResponse, error) {
ret := _m.Called(_a0, _a1)
@@ -3549,6 +3602,65 @@ func (_c *MockProxy_DumpMessages_Call) RunAndReturn(run func(*milvuspb.DumpMessa
return _c
}
// ExportSnapshot provides a mock function with given fields: _a0, _a1
func (_m *MockProxy) ExportSnapshot(_a0 context.Context, _a1 *milvuspb.ExportSnapshotRequest) (*milvuspb.ExportSnapshotResponse, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for ExportSnapshot")
}
var r0 *milvuspb.ExportSnapshotResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.ExportSnapshotRequest) (*milvuspb.ExportSnapshotResponse, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.ExportSnapshotRequest) *milvuspb.ExportSnapshotResponse); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*milvuspb.ExportSnapshotResponse)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.ExportSnapshotRequest) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockProxy_ExportSnapshot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExportSnapshot'
type MockProxy_ExportSnapshot_Call struct {
*mock.Call
}
// ExportSnapshot is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *milvuspb.ExportSnapshotRequest
func (_e *MockProxy_Expecter) ExportSnapshot(_a0 interface{}, _a1 interface{}) *MockProxy_ExportSnapshot_Call {
return &MockProxy_ExportSnapshot_Call{Call: _e.mock.On("ExportSnapshot", _a0, _a1)}
}
func (_c *MockProxy_ExportSnapshot_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.ExportSnapshotRequest)) *MockProxy_ExportSnapshot_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*milvuspb.ExportSnapshotRequest))
})
return _c
}
func (_c *MockProxy_ExportSnapshot_Call) Return(_a0 *milvuspb.ExportSnapshotResponse, _a1 error) *MockProxy_ExportSnapshot_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockProxy_ExportSnapshot_Call) RunAndReturn(run func(context.Context, *milvuspb.ExportSnapshotRequest) (*milvuspb.ExportSnapshotResponse, error)) *MockProxy_ExportSnapshot_Call {
_c.Call.Return(run)
return _c
}
// Flush provides a mock function with given fields: _a0, _a1
func (_m *MockProxy) Flush(_a0 context.Context, _a1 *milvuspb.FlushRequest) (*milvuspb.FlushResponse, error) {
ret := _m.Called(_a0, _a1)
@@ -8166,6 +8278,65 @@ func (_c *MockProxy_ReplicateMessage_Call) RunAndReturn(run func(context.Context
return _c
}
// RestoreExternalSnapshot provides a mock function with given fields: _a0, _a1
func (_m *MockProxy) RestoreExternalSnapshot(_a0 context.Context, _a1 *milvuspb.RestoreExternalSnapshotRequest) (*milvuspb.RestoreExternalSnapshotResponse, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for RestoreExternalSnapshot")
}
var r0 *milvuspb.RestoreExternalSnapshotResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.RestoreExternalSnapshotRequest) (*milvuspb.RestoreExternalSnapshotResponse, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.RestoreExternalSnapshotRequest) *milvuspb.RestoreExternalSnapshotResponse); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*milvuspb.RestoreExternalSnapshotResponse)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.RestoreExternalSnapshotRequest) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockProxy_RestoreExternalSnapshot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RestoreExternalSnapshot'
type MockProxy_RestoreExternalSnapshot_Call struct {
*mock.Call
}
// RestoreExternalSnapshot is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *milvuspb.RestoreExternalSnapshotRequest
func (_e *MockProxy_Expecter) RestoreExternalSnapshot(_a0 interface{}, _a1 interface{}) *MockProxy_RestoreExternalSnapshot_Call {
return &MockProxy_RestoreExternalSnapshot_Call{Call: _e.mock.On("RestoreExternalSnapshot", _a0, _a1)}
}
func (_c *MockProxy_RestoreExternalSnapshot_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.RestoreExternalSnapshotRequest)) *MockProxy_RestoreExternalSnapshot_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*milvuspb.RestoreExternalSnapshotRequest))
})
return _c
}
func (_c *MockProxy_RestoreExternalSnapshot_Call) Return(_a0 *milvuspb.RestoreExternalSnapshotResponse, _a1 error) *MockProxy_RestoreExternalSnapshot_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockProxy_RestoreExternalSnapshot_Call) RunAndReturn(run func(context.Context, *milvuspb.RestoreExternalSnapshotRequest) (*milvuspb.RestoreExternalSnapshotResponse, error)) *MockProxy_RestoreExternalSnapshot_Call {
_c.Call.Return(run)
return _c
}
// RestoreRBAC provides a mock function with given fields: _a0, _a1
func (_m *MockProxy) RestoreRBAC(_a0 context.Context, _a1 *milvuspb.RestoreRBACMetaRequest) (*commonpb.Status, error) {
ret := _m.Called(_a0, _a1)
@@ -0,0 +1,265 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proxy
import (
"context"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
"github.com/milvus-io/milvus-proto/go-api/v3/milvuspb"
"github.com/milvus-io/milvus/internal/mocks"
"github.com/milvus-io/milvus/pkg/v3/proto/internalpb"
"github.com/milvus-io/milvus/pkg/v3/util/crypto"
"github.com/milvus-io/milvus/pkg/v3/util/merr"
"github.com/milvus-io/milvus/pkg/v3/util/paramtable"
)
func TestCreateCredentialPassesDescription(t *testing.T) {
paramtable.Init()
description := "created user description"
mixCoord := mocks.NewMockMixCoordClient(t)
mixCoord.EXPECT().
CreateCredential(mock.Anything, mock.MatchedBy(func(cred *internalpb.CredentialInfo) bool {
return cred.GetUsername() == "desc_user" &&
cred.Description != nil &&
cred.GetDescription() == description &&
cred.GetEncryptedPassword() != "" &&
cred.GetSha256Password() != ""
})).
Return(merr.Success(), nil)
node := &Proxy{mixCoord: mixCoord}
node.UpdateStateCode(commonpb.StateCode_Healthy)
status, err := node.CreateCredential(context.Background(), &milvuspb.CreateCredentialRequest{
Username: "desc_user",
Password: crypto.Base64Encode("password"),
Description: &description,
})
require.NoError(t, err)
assert.Equal(t, commonpb.ErrorCode_Success, status.GetErrorCode())
}
func TestCreateCredentialRejectsOverLimitDescription(t *testing.T) {
paramtable.Init()
mixCoord := mocks.NewMockMixCoordClient(t)
mixCoord.On("CreateCredential", mock.Anything, mock.Anything).Return(merr.Success(), nil).Maybe()
node := &Proxy{mixCoord: mixCoord}
node.UpdateStateCode(commonpb.StateCode_Healthy)
description := strings.Repeat("x", 1025)
status, err := node.CreateCredential(context.Background(), &milvuspb.CreateCredentialRequest{
Username: "desc_user",
Password: crypto.Base64Encode("password"),
Description: &description,
})
require.NoError(t, err)
assert.NotEqual(t, commonpb.ErrorCode_Success, status.GetErrorCode())
mixCoord.AssertNotCalled(t, "CreateCredential", mock.Anything, mock.Anything)
}
func TestUpdateCredentialDescriptionOnlySkipsPasswordVerification(t *testing.T) {
paramtable.Init()
description := "updated user description"
mixCoord := mocks.NewMockMixCoordClient(t)
mixCoord.EXPECT().
UpdateCredential(mock.Anything, mock.MatchedBy(func(cred *internalpb.CredentialInfo) bool {
return cred.GetUsername() == "desc_user" &&
cred.Description != nil &&
cred.GetDescription() == description &&
cred.GetEncryptedPassword() == "" &&
cred.GetSha256Password() == ""
})).
Return(merr.Success(), nil)
node := &Proxy{mixCoord: mixCoord}
node.UpdateStateCode(commonpb.StateCode_Healthy)
status, err := node.UpdateCredential(context.Background(), &milvuspb.UpdateCredentialRequest{
Username: "desc_user",
Description: &description,
})
require.NoError(t, err)
assert.Equal(t, commonpb.ErrorCode_Success, status.GetErrorCode())
}
func TestUpdateCredentialClearsDescriptionOnly(t *testing.T) {
paramtable.Init()
description := ""
mixCoord := mocks.NewMockMixCoordClient(t)
mixCoord.EXPECT().
UpdateCredential(mock.Anything, mock.MatchedBy(func(cred *internalpb.CredentialInfo) bool {
return cred.GetUsername() == "desc_user" &&
cred.Description != nil &&
cred.GetDescription() == description &&
cred.GetEncryptedPassword() == "" &&
cred.GetSha256Password() == ""
})).
Return(merr.Success(), nil)
node := &Proxy{mixCoord: mixCoord}
node.UpdateStateCode(commonpb.StateCode_Healthy)
status, err := node.UpdateCredential(context.Background(), &milvuspb.UpdateCredentialRequest{
Username: "desc_user",
Description: &description,
})
require.NoError(t, err)
assert.Equal(t, commonpb.ErrorCode_Success, status.GetErrorCode())
}
func TestUpdateCredentialRejectsEmptyUpdate(t *testing.T) {
paramtable.Init()
mixCoord := mocks.NewMockMixCoordClient(t)
mixCoord.On("UpdateCredential", mock.Anything, mock.Anything).Return(merr.Success(), nil).Maybe()
node := &Proxy{mixCoord: mixCoord}
node.UpdateStateCode(commonpb.StateCode_Healthy)
status, err := node.UpdateCredential(context.Background(), &milvuspb.UpdateCredentialRequest{
Username: "desc_user",
})
require.NoError(t, err)
assert.NotEqual(t, commonpb.ErrorCode_Success, status.GetErrorCode())
assert.Contains(t, status.GetReason(), "must update either password or description")
mixCoord.AssertNotCalled(t, "UpdateCredential", mock.Anything, mock.Anything)
}
func TestUpdateCredentialPasswordAndDescription(t *testing.T) {
paramtable.Init()
require.NoError(t, paramtable.Get().Save(Params.CommonCfg.SuperUsers.Key, "root"))
defer paramtable.Get().Reset(Params.CommonCfg.SuperUsers.Key)
description := "updated user description"
mixCoord := mocks.NewMockMixCoordClient(t)
mixCoord.EXPECT().
UpdateCredential(mock.Anything, mock.MatchedBy(func(cred *internalpb.CredentialInfo) bool {
return cred.GetUsername() == "desc_user" &&
cred.Description != nil &&
cred.GetDescription() == description &&
cred.GetEncryptedPassword() != "" &&
cred.GetSha256Password() == crypto.SHA256("new_password", "desc_user")
})).
Return(merr.Success(), nil)
node := &Proxy{mixCoord: mixCoord}
node.UpdateStateCode(commonpb.StateCode_Healthy)
status, err := node.UpdateCredential(GetContext(context.Background(), "root:password"), &milvuspb.UpdateCredentialRequest{
Username: "desc_user",
OldPassword: crypto.Base64Encode("old_password"),
NewPassword: crypto.Base64Encode("new_password"),
Description: &description,
})
require.NoError(t, err)
assert.Equal(t, commonpb.ErrorCode_Success, status.GetErrorCode())
}
func TestUpdateCredentialRejectsOverLimitDescription(t *testing.T) {
paramtable.Init()
require.NoError(t, paramtable.Get().Save(Params.CommonCfg.SuperUsers.Key, "root"))
defer paramtable.Get().Reset(Params.CommonCfg.SuperUsers.Key)
mixCoord := mocks.NewMockMixCoordClient(t)
mixCoord.On("UpdateCredential", mock.Anything, mock.Anything).Return(merr.Success(), nil).Maybe()
node := &Proxy{mixCoord: mixCoord}
node.UpdateStateCode(commonpb.StateCode_Healthy)
description := strings.Repeat("x", 1025)
status, err := node.UpdateCredential(GetContext(context.Background(), "root:password"), &milvuspb.UpdateCredentialRequest{
Username: "desc_user",
OldPassword: crypto.Base64Encode("old_password"),
NewPassword: crypto.Base64Encode("new_password"),
Description: &description,
})
require.NoError(t, err)
assert.NotEqual(t, commonpb.ErrorCode_Success, status.GetErrorCode())
mixCoord.AssertNotCalled(t, "UpdateCredential", mock.Anything, mock.Anything)
}
func TestUpdateCredentialPasswordValidationErrors(t *testing.T) {
paramtable.Init()
tests := []struct {
name string
oldPassword string
newPassword string
reason string
}{
{
name: "invalid old password base64",
oldPassword: "not-base64!!",
newPassword: crypto.Base64Encode("new_password"),
reason: "decode old password failed",
},
{
name: "invalid new password base64",
oldPassword: crypto.Base64Encode("old_password"),
newPassword: "not-base64!!",
reason: "decode password failed",
},
{
name: "invalid new password length",
oldPassword: crypto.Base64Encode("old_password"),
newPassword: crypto.Base64Encode("short"),
reason: "invalid password length",
},
{
name: "old password verification failed",
oldPassword: crypto.Base64Encode("old_password"),
newPassword: crypto.Base64Encode("new_password"),
reason: "old password not correct",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mixCoord := mocks.NewMockMixCoordClient(t)
mixCoord.On("UpdateCredential", mock.Anything, mock.Anything).Return(merr.Success(), nil).Maybe()
node := &Proxy{mixCoord: mixCoord}
node.UpdateStateCode(commonpb.StateCode_Healthy)
status, err := node.UpdateCredential(context.Background(), &milvuspb.UpdateCredentialRequest{
Username: "desc_user",
OldPassword: test.oldPassword,
NewPassword: test.newPassword,
})
require.NoError(t, err)
assert.NotEqual(t, commonpb.ErrorCode_Success, status.GetErrorCode())
assert.Contains(t, status.GetReason(), test.reason)
mixCoord.AssertNotCalled(t, "UpdateCredential", mock.Anything, mock.Anything)
})
}
}
+54 -38
View File
@@ -5483,6 +5483,9 @@ func (node *Proxy) CreateCredential(ctx context.Context, req *milvuspb.CreateCre
if err := ValidateUsername(username); err != nil {
return merr.Status(err), nil
}
if err := ValidateUserDescription(req.GetDescription()); err != nil {
return merr.Status(err), nil
}
rawPassword, err := crypto.Base64Decode(req.Password)
if err != nil {
log.Error("decode password fail",
@@ -5511,6 +5514,7 @@ func (node *Proxy) CreateCredential(ctx context.Context, req *milvuspb.CreateCre
Username: req.Username,
EncryptedPassword: encryptedPassword,
Sha256Password: crypto.SHA256(rawPassword, req.Username),
Description: req.Description,
}
result, err := node.mixCoord.CreateCredential(ctx, credInfo)
if err != nil { // for error like conntext timeout etc.
@@ -5533,57 +5537,69 @@ func (node *Proxy) UpdateCredential(ctx context.Context, req *milvuspb.UpdateCre
if err := merr.CheckHealthy(node.GetStateCode()); err != nil {
return merr.Status(err), nil
}
rawOldPassword, err := crypto.Base64Decode(req.OldPassword)
if err != nil {
log.Error("decode old password fail",
zap.Error(err))
err = merr.WrapErrParameterInvalidErr(err, "decode old password failed")
if err := ValidateUserDescription(req.GetDescription()); err != nil {
return merr.Status(err), nil
}
rawNewPassword, err := crypto.Base64Decode(req.NewPassword)
if err != nil {
log.Error("decode password fail",
zap.Error(err))
err = merr.WrapErrParameterInvalidErr(err, "decode password failed")
return merr.Status(err), nil
}
// valid new password
if err = ValidatePassword(rawNewPassword); err != nil {
log.Error("illegal password",
zap.Error(err))
if req.GetNewPassword() == "" && req.Description == nil {
err := merr.WrapErrParameterInvalidMsg("must update either password or description")
return merr.Status(err), nil
}
skipPasswordVerify := false
if currentUser, _ := GetCurUserFromContext(ctx); currentUser != "" {
for _, s := range Params.CommonCfg.SuperUsers.GetAsStrings() {
if s == currentUser {
skipPasswordVerify = true
updateCredReq := &internalpb.CredentialInfo{
Username: req.Username,
Description: req.Description,
}
if req.GetNewPassword() != "" {
rawOldPassword, err := crypto.Base64Decode(req.OldPassword)
if err != nil {
log.Error("decode old password fail",
zap.Error(err))
err = merr.WrapErrParameterInvalidErr(err, "decode old password failed")
return merr.Status(err), nil
}
rawNewPassword, err := crypto.Base64Decode(req.NewPassword)
if err != nil {
log.Error("decode password fail",
zap.Error(err))
err = merr.WrapErrParameterInvalidErr(err, "decode password failed")
return merr.Status(err), nil
}
// valid new password
if err = ValidatePassword(rawNewPassword); err != nil {
log.Error("illegal password",
zap.Error(err))
return merr.Status(err), nil
}
skipPasswordVerify := false
if currentUser, _ := GetCurUserFromContext(ctx); currentUser != "" {
for _, s := range Params.CommonCfg.SuperUsers.GetAsStrings() {
if s == currentUser {
skipPasswordVerify = true
}
}
}
}
if !skipPasswordVerify && !passwordVerify(ctx, req.Username, rawOldPassword, privilege.GetPrivilegeCache()) {
err := merr.WrapErrPrivilegeNotAuthenticated("old password not correct for %s", req.GetUsername())
return merr.Status(err), nil
}
// update meta data
encryptedPassword, err := crypto.PasswordEncrypt(rawNewPassword)
if err != nil {
log.Error("encrypt password fail",
zap.Error(err))
err = merr.WrapErrServiceInternalErr(err, "encrypt password failed")
return merr.Status(err), nil
if !skipPasswordVerify && !passwordVerify(ctx, req.Username, rawOldPassword, privilege.GetPrivilegeCache()) {
err := merr.WrapErrPrivilegeNotAuthenticated("old password not correct for %s", req.GetUsername())
return merr.Status(err), nil
}
// update meta data
encryptedPassword, err := crypto.PasswordEncrypt(rawNewPassword)
if err != nil {
log.Error("encrypt password fail",
zap.Error(err))
err = merr.WrapErrServiceInternalErr(err, "encrypt password failed")
return merr.Status(err), nil
}
updateCredReq.Sha256Password = crypto.SHA256(rawNewPassword, req.Username)
updateCredReq.EncryptedPassword = encryptedPassword
}
if req.Base == nil {
req.Base = &commonpb.MsgBase{}
}
req.Base.MsgType = commonpb.MsgType_UpdateCredential
updateCredReq := &internalpb.CredentialInfo{
Username: req.Username,
Sha256Password: crypto.SHA256(rawNewPassword, req.Username),
EncryptedPassword: encryptedPassword,
}
result, err := node.mixCoord.UpdateCredential(ctx, updateCredReq)
if err != nil { // for error like conntext timeout etc.
log.Error("update credential fail",
+7
View File
@@ -1486,6 +1486,13 @@ func ValidateUsername(username string) error {
return nil
}
func ValidateUserDescription(description string) error {
if len(description) > Params.ProxyCfg.MaxUserDescriptionLength.GetAsInt() {
return merr.WrapErrParameterInvalidMsg("invalid user description with length %d, the length of user description must be less than or equal to %s", len(description), Params.ProxyCfg.MaxUserDescriptionLength.GetValue())
}
return nil
}
func ValidatePassword(password string) error {
if len(password) < Params.ProxyCfg.MinPasswordLength.GetAsInt() || len(password) > Params.ProxyCfg.MaxPasswordLength.GetAsInt() {
return merr.WrapErrParameterInvalidRange(Params.ProxyCfg.MinPasswordLength.GetAsInt(),
@@ -88,8 +88,10 @@ func (c *DDLCallback) alterUserV2AckCallback(ctx context.Context, result message
return merr.Wrap(err, "failed to alter credential")
}
// update proxy's local cache
if err := c.UpdateCredCache(ctx, result.Message.MustBody().CredentialInfo); err != nil {
return merr.Wrap(err, "failed to update cred cache")
if result.Message.MustBody().CredentialInfo.GetSha256Password() != "" {
if err := c.UpdateCredCache(ctx, result.Message.MustBody().CredentialInfo); err != nil {
return merr.Wrap(err, "failed to update cred cache")
}
}
return nil
}
@@ -18,14 +18,20 @@ package rootcoord
import (
"context"
"sync/atomic"
"testing"
"github.com/cockroachdb/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
"github.com/milvus-io/milvus-proto/go-api/v3/milvuspb"
"github.com/milvus-io/milvus/internal/util/proxyutil"
"github.com/milvus-io/milvus/pkg/v3/proto/internalpb"
"github.com/milvus-io/milvus/pkg/v3/proto/proxypb"
"github.com/milvus-io/milvus/pkg/v3/proto/rootcoordpb"
"github.com/milvus-io/milvus/pkg/v3/streaming/util/message"
"github.com/milvus-io/milvus/pkg/v3/util/funcutil"
"github.com/milvus-io/milvus/pkg/v3/util/merr"
)
@@ -70,6 +76,7 @@ func TestDDLCallbacksRBACCredential(t *testing.T) {
status, err = core.UpdateCredential(context.Background(), &internalpb.CredentialInfo{
Username: testUserName,
EncryptedPassword: "1234567",
Sha256Password: "1234567-sha256",
})
require.NoError(t, merr.CheckRPCCall(status, err))
getCredentialResp, err = core.GetCredential(context.Background(), &rootcoordpb.GetCredentialRequest{
@@ -88,3 +95,81 @@ func TestDDLCallbacksRBACCredential(t *testing.T) {
})
require.Error(t, merr.CheckRPCCall(getCredentialResp.Status, err))
}
func TestAlterUserV2AckCallbackSkipsCredentialCacheForDescriptionOnlyUpdate(t *testing.T) {
description := "description only"
var updateCacheCalls atomic.Int32
meta := newMockMetaTable()
meta.AlterCredentialFunc = func(ctx context.Context, msg message.BroadcastResultAlterUserMessageV2) error {
return nil
}
core := newTestCore(withMeta(meta))
core.proxyClientManager = proxyutil.NewProxyClientManager(proxyutil.DefaultProxyCreator)
proxy := newMockProxy()
proxy.UpdateCredentialCacheFunc = func(ctx context.Context, request *proxypb.UpdateCredCacheRequest) (*commonpb.Status, error) {
updateCacheCalls.Add(1)
return merr.Success(), nil
}
core.proxyClientManager.GetProxyClients().Insert(TestProxyID, proxy)
err := (&DDLCallback{Core: core}).alterUserV2AckCallback(context.Background(), buildAlterUserMessage(&internalpb.CredentialInfo{
Username: "desc_user",
Description: &description,
}, 1))
require.NoError(t, err)
assert.Zero(t, updateCacheCalls.Load())
}
func TestAlterUserV2AckCallbackUpdatesCredentialCacheForPasswordUpdate(t *testing.T) {
var updateCacheCalls atomic.Int32
meta := newMockMetaTable()
meta.AlterCredentialFunc = func(ctx context.Context, msg message.BroadcastResultAlterUserMessageV2) error {
return nil
}
core := newTestCore(withMeta(meta))
core.proxyClientManager = proxyutil.NewProxyClientManager(proxyutil.DefaultProxyCreator)
proxy := newMockProxy()
proxy.UpdateCredentialCacheFunc = func(ctx context.Context, request *proxypb.UpdateCredCacheRequest) (*commonpb.Status, error) {
updateCacheCalls.Add(1)
assert.Equal(t, "desc_user", request.GetUsername())
assert.Equal(t, "sha256", request.GetPassword())
return merr.Success(), nil
}
core.proxyClientManager.GetProxyClients().Insert(TestProxyID, proxy)
err := (&DDLCallback{Core: core}).alterUserV2AckCallback(context.Background(), buildAlterUserMessage(&internalpb.CredentialInfo{
Username: "desc_user",
Sha256Password: "sha256",
}, 1))
require.NoError(t, err)
assert.Equal(t, int32(1), updateCacheCalls.Load())
}
func TestAlterUserV2AckCallbackReturnsCredentialCacheError(t *testing.T) {
meta := newMockMetaTable()
meta.AlterCredentialFunc = func(ctx context.Context, msg message.BroadcastResultAlterUserMessageV2) error {
return nil
}
core := newTestCore(withMeta(meta))
core.proxyClientManager = proxyutil.NewProxyClientManager(proxyutil.DefaultProxyCreator)
proxy := newMockProxy()
proxy.UpdateCredentialCacheFunc = func(ctx context.Context, request *proxypb.UpdateCredCacheRequest) (*commonpb.Status, error) {
return nil, errors.New("cache update failed")
}
core.proxyClientManager.GetProxyClients().Insert(TestProxyID, proxy)
err := (&DDLCallback{Core: core}).alterUserV2AckCallback(context.Background(), buildAlterUserMessage(&internalpb.CredentialInfo{
Username: "desc_user",
Sha256Password: "sha256",
}, 1))
require.Error(t, err)
assert.ErrorContains(t, err, "failed to update cred cache")
}
+20 -1
View File
@@ -1741,6 +1741,14 @@ func (mt *MetaTable) CheckIfUpdateCredential(ctx context.Context, credInfo *inte
if funcutil.IsEmptyString(credInfo.GetUsername()) {
return merr.WrapErrParameterInvalidMsg("username is empty")
}
hasEncryptedPassword := credInfo.GetEncryptedPassword() != ""
hasSha256Password := credInfo.GetSha256Password() != ""
if hasEncryptedPassword != hasSha256Password {
return merr.WrapErrParameterInvalidMsg("credential password update must include both encrypted and sha256 password")
}
if !hasEncryptedPassword && !hasSha256Password && credInfo.Description == nil {
return merr.WrapErrParameterInvalidMsg("credential update must change password or description")
}
mt.permissionLock.RLock()
defer mt.permissionLock.RUnlock()
@@ -1774,9 +1782,20 @@ func (mt *MetaTable) AlterCredential(ctx context.Context, result message.Broadca
)
return nil
}
encryptedPassword := body.CredentialInfo.EncryptedPassword
description := body.CredentialInfo.GetDescription()
if existsCredential != nil {
if encryptedPassword == "" {
encryptedPassword = existsCredential.EncryptedPassword
}
if body.CredentialInfo.Description == nil {
description = existsCredential.Description
}
}
credential := &model.Credential{
Username: body.CredentialInfo.Username,
EncryptedPassword: body.CredentialInfo.EncryptedPassword,
EncryptedPassword: encryptedPassword,
Description: description,
TimeTick: result.GetControlChannelResult().TimeTick,
}
return mt.catalog.AlterCredential(ctx, credential)
+84
View File
@@ -162,6 +162,90 @@ func TestRbacCredential(t *testing.T) {
require.NoError(t, err)
}
func TestRbacCredentialAlterCredentialMergesPartialUpdates(t *testing.T) {
mt := generateMetaTable(t)
ptr := func(s string) *string {
return &s
}
username := "user" + funcutil.RandomString(10)
err := mt.AlterCredential(context.TODO(), buildAlterUserMessage(&internalpb.CredentialInfo{
Username: username,
EncryptedPassword: "old-password",
Description: ptr("initial description"),
}, 1))
require.NoError(t, err)
err = mt.AlterCredential(context.TODO(), buildAlterUserMessage(&internalpb.CredentialInfo{
Username: username,
EncryptedPassword: "new-password",
}, 2))
require.NoError(t, err)
cred, err := mt.GetCredential(context.TODO(), username)
require.NoError(t, err)
assert.Equal(t, "new-password", cred.GetEncryptedPassword())
assert.Equal(t, "initial description", cred.GetDescription())
err = mt.AlterCredential(context.TODO(), buildAlterUserMessage(&internalpb.CredentialInfo{
Username: username,
Description: ptr("updated description"),
}, 3))
require.NoError(t, err)
cred, err = mt.GetCredential(context.TODO(), username)
require.NoError(t, err)
assert.Equal(t, "new-password", cred.GetEncryptedPassword())
assert.Equal(t, "updated description", cred.GetDescription())
err = mt.AlterCredential(context.TODO(), buildAlterUserMessage(&internalpb.CredentialInfo{
Username: username,
Description: ptr(""),
}, 4))
require.NoError(t, err)
cred, err = mt.GetCredential(context.TODO(), username)
require.NoError(t, err)
assert.Equal(t, "new-password", cred.GetEncryptedPassword())
assert.Equal(t, "", cred.GetDescription())
}
func TestRbacCredentialRejectsInconsistentPasswordUpdate(t *testing.T) {
mt := generateMetaTable(t)
username := "user" + funcutil.RandomString(10)
err := mt.AlterCredential(context.TODO(), buildAlterUserMessage(&internalpb.CredentialInfo{
Username: username,
EncryptedPassword: "old-password",
}, 1))
require.NoError(t, err)
err = mt.CheckIfUpdateCredential(context.TODO(), &internalpb.CredentialInfo{
Username: username,
EncryptedPassword: "new-password",
})
require.Error(t, err)
assert.ErrorContains(t, err, "must include both encrypted and sha256 password")
err = mt.CheckIfUpdateCredential(context.TODO(), &internalpb.CredentialInfo{
Username: username,
Sha256Password: "sha256",
})
require.Error(t, err)
assert.ErrorContains(t, err, "must include both encrypted and sha256 password")
description := "description-only update"
err = mt.CheckIfUpdateCredential(context.TODO(), &internalpb.CredentialInfo{
Username: username,
Description: &description,
})
require.NoError(t, err)
err = mt.CheckIfUpdateCredential(context.TODO(), &internalpb.CredentialInfo{
Username: username,
})
require.Error(t, err)
assert.ErrorContains(t, err, "credential update must change password or description")
}
func TestRbacCreateRole(t *testing.T) {
mt := generateMetaTable(t)
-2
View File
@@ -2199,7 +2199,6 @@ func (c *Core) CreateCredential(ctx context.Context, credInfo *internalpb.Creden
if err := merr.CheckHealthy(c.GetStateCode()); err != nil {
return merr.Status(err), nil
}
if err := c.broadcastAlterUserForCreateCredential(ctx, credInfo); err != nil {
ctxLog.Warn("CreateCredential failed", zap.Error(err))
metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.FailLabel).Inc()
@@ -2255,7 +2254,6 @@ func (c *Core) UpdateCredential(ctx context.Context, credInfo *internalpb.Creden
if err := merr.CheckHealthy(c.GetStateCode()); err != nil {
return merr.Status(err), nil
}
if err := c.broadcastAlterUserForUpdateCredential(ctx, credInfo); err != nil {
ctxLog.Warn("UpdateCredential append message failed", zap.Error(err))
metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.FailLabel).Inc()
+1 -1
View File
@@ -22,7 +22,7 @@ require (
github.com/jolestar/go-commons-pool/v2 v2.1.2
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12
github.com/klauspost/compress v1.18.0
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260514121806-9b60a053353f
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260612100615-43795e8f8f6e
github.com/minio/minio-go/v7 v7.0.73
github.com/panjf2000/ants/v2 v2.11.3
github.com/prometheus/client_golang v1.20.5
+2 -2
View File
@@ -498,8 +498,8 @@ github.com/milvus-io/cgosymbolizer v0.0.0-20250318084424-114f4050c3a6 h1:YHMFI6L
github.com/milvus-io/cgosymbolizer v0.0.0-20250318084424-114f4050c3a6/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg=
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b h1:TfeY0NxYxZzUfIfYe5qYDBzt4ZYRqzUjTR6CvUzjat8=
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b/go.mod h1:iwW+9cWfIzzDseEBCCeDSN5SD16Tidvy8cwQ7ZY8Qj4=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260514121806-9b60a053353f h1:Z2paGeFL0i1a3Dqw82DwRwZ2ix3FtyKU7+u6rR7jbUs=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260514121806-9b60a053353f/go.mod h1:rbKpv5JToISTKTTLl0duL5r6wbYnjJ9SsD0QgXMzKy0=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260612100615-43795e8f8f6e h1:axPnOhjsWqqxk0dOQRSIU2mdg52Kyl/8E6iDFZy/IpE=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260612100615-43795e8f8f6e/go.mod h1:rbKpv5JToISTKTTLl0duL5r6wbYnjJ9SsD0QgXMzKy0=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.73 h1:qr2vi96Qm7kZ4v7LLebjte+MQh621fFWnv93p12htEo=
+1
View File
@@ -329,6 +329,7 @@ message CredentialInfo {
// encrypted by sha256 (for good performance in cache mapping)
string sha256_password = 5; // not save in metadata.
uint64 time_tick = 6; // the timetick in wal which the credential updates
optional string description = 7;
}
message ListPolicyRequest {
+308 -295
View File
@@ -2945,8 +2945,9 @@ type CredentialInfo struct {
// Deprecated: Marked as deprecated in internal.proto.
IsSuper bool `protobuf:"varint,4,opt,name=is_super,json=isSuper,proto3" json:"is_super,omitempty"` // not used.
// encrypted by sha256 (for good performance in cache mapping)
Sha256Password string `protobuf:"bytes,5,opt,name=sha256_password,json=sha256Password,proto3" json:"sha256_password,omitempty"` // not save in metadata.
TimeTick uint64 `protobuf:"varint,6,opt,name=time_tick,json=timeTick,proto3" json:"time_tick,omitempty"` // the timetick in wal which the credential updates
Sha256Password string `protobuf:"bytes,5,opt,name=sha256_password,json=sha256Password,proto3" json:"sha256_password,omitempty"` // not save in metadata.
TimeTick uint64 `protobuf:"varint,6,opt,name=time_tick,json=timeTick,proto3" json:"time_tick,omitempty"` // the timetick in wal which the credential updates
Description *string `protobuf:"bytes,7,opt,name=description,proto3,oneof" json:"description,omitempty"`
}
func (x *CredentialInfo) Reset() {
@@ -3025,6 +3026,13 @@ func (x *CredentialInfo) GetTimeTick() uint64 {
return 0
}
func (x *CredentialInfo) GetDescription() string {
if x != nil && x.Description != nil {
return *x.Description
}
return ""
}
type ListPolicyRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -5292,7 +5300,7 @@ var file_internal_proto_rawDesc = []byte{
0x20, 0x03, 0x28, 0x04, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73,
0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x64, 0x65, 0x66,
0x61, 0x75, 0x6c, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xdc, 0x01,
0x61, 0x75, 0x6c, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x93, 0x02,
0x0a, 0x0e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f,
0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x12,
@@ -5306,304 +5314,308 @@ var file_internal_proto_rawDesc = []byte{
0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0e, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12,
0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x69, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01,
0x28, 0x04, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x54, 0x69, 0x63, 0x6b, 0x22, 0x45, 0x0a, 0x11,
0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x30, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1c, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x73, 0x67, 0x42, 0x61, 0x73, 0x65, 0x52, 0x04, 0x62,
0x61, 0x73, 0x65, 0x22, 0xdf, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69,
0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74,
0x28, 0x04, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x54, 0x69, 0x63, 0x6b, 0x12, 0x25, 0x0a, 0x0b,
0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28,
0x09, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x22, 0x45, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63,
0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x73, 0x67,
0x42, 0x61, 0x73, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x22, 0xdf, 0x01, 0x0a, 0x12, 0x4c,
0x69, 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06,
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79,
0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x6f,
0x6c, 0x69, 0x63, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65,
0x72, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75,
0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x52, 0x0a, 0x10, 0x70, 0x72, 0x69, 0x76,
0x69, 0x6c, 0x65, 0x67, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65,
0x67, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x70, 0x72, 0x69,
0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x22, 0x67, 0x0a, 0x19,
0x53, 0x68, 0x6f, 0x77, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x04, 0x62, 0x61, 0x73,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x73,
0x67, 0x42, 0x61, 0x73, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70,
0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61,
0x74, 0x74, 0x65, 0x72, 0x6e, 0x22, 0x9a, 0x01, 0x0a, 0x1a, 0x53, 0x68, 0x6f, 0x77, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x47, 0x0a, 0x0d, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x50,
0x61, 0x69, 0x72, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x22, 0x45, 0x0a, 0x04, 0x52, 0x61, 0x74, 0x65, 0x12, 0x2f, 0x0a, 0x02, 0x72, 0x74,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x52,
0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x02, 0x72, 0x74, 0x12, 0x0c, 0x0a, 0x01, 0x72,
0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x01, 0x72, 0x22, 0x32, 0x0a, 0x0a, 0x49, 0x6d, 0x70,
0x6f, 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73,
0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x22, 0xb7, 0x03,
0x0a, 0x15, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49,
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x04, 0x64, 0x62, 0x49, 0x44, 0x18,
0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x04, 0x64, 0x62, 0x49, 0x44, 0x12,
0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18,
0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x49, 0x44, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f,
0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c,
0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x18, 0x04, 0x20, 0x03,
0x28, 0x03, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73,
0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18,
0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x6f, 0x6c, 0x6c,
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x06, 0x73, 0x63,
0x68, 0x65, 0x6d, 0x61, 0x12, 0x37, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x49, 0x6d, 0x70, 0x6f,
0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x3b, 0x0a,
0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21,
0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f,
0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x50, 0x61, 0x69,
0x72, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x61,
0x74, 0x61, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x09, 0x20, 0x01,
0x28, 0x04, 0x52, 0x0d, 0x64, 0x61, 0x74, 0x61, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x70, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03,
0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x22, 0xee, 0x01, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6f,
0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x62, 0x5f,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x62, 0x4e, 0x61,
0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c,
0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x70,
0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61,
0x6d, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74,
0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x07, 0x6f,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d,
0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x6f, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52,
0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x5b, 0x0a, 0x0e, 0x49, 0x6d, 0x70, 0x6f,
0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74,
0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c,
0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
0x21, 0x0a, 0x0c, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x18,
0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x6e, 0x66,
0x6f, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x73,
0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65,
0x73, 0x12, 0x52, 0x0a, 0x10, 0x70, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x5f, 0x67,
0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6d, 0x69,
0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75,
0x73, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70,
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x70, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x47,
0x72, 0x6f, 0x75, 0x70, 0x73, 0x22, 0x67, 0x0a, 0x19, 0x53, 0x68, 0x6f, 0x77, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x30, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1c, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x73, 0x67, 0x42, 0x61, 0x73, 0x65, 0x52, 0x04,
0x62, 0x61, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x22, 0x9a,
0x01, 0x0a, 0x1a, 0x53, 0x68, 0x6f, 0x77, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a,
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e,
0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74,
0x75, 0x73, 0x12, 0x47, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76,
0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x0d, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x45, 0x0a, 0x04, 0x52,
0x61, 0x74, 0x65, 0x12, 0x2f, 0x0a, 0x02, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x1f, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69,
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x52, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65,
0x52, 0x02, 0x72, 0x74, 0x12, 0x0c, 0x0a, 0x01, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52,
0x01, 0x72, 0x22, 0x32, 0x0a, 0x0a, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65,
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64,
0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x22, 0xb7, 0x03, 0x0a, 0x15, 0x49, 0x6d, 0x70, 0x6f, 0x72,
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
0x12, 0x16, 0x0a, 0x04, 0x64, 0x62, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02,
0x18, 0x01, 0x52, 0x04, 0x64, 0x62, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c,
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c,
0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x27, 0x0a, 0x0f,
0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69,
0x6f, 0x6e, 0x49, 0x44, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x61, 0x72,
0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09,
0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x3d,
0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25,
0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x63,
0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53,
0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x37, 0x0a,
0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d,
0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x52,
0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4b, 0x65,
0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x64, 0x61, 0x74,
0x61, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f,
0x62, 0x49, 0x44, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44,
0x22, 0xee, 0x01, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63,
0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f,
0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x61,
0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x66,
0x69, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c,
0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
0x61, 0x6c, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x66,
0x69, 0x6c, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,
0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x56,
0x61, 0x6c, 0x75, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x73, 0x22, 0x5b, 0x0a, 0x0e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49,
0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x22, 0x49,
0x0a, 0x18, 0x47, 0x65, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72,
0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x62,
0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x62, 0x4e,
0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x22, 0x81, 0x02, 0x0a, 0x12, 0x49, 0x6d,
0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73,
0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a,
0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65,
0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73,
0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04,
0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x23,
0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18,
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x54,
0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6d, 0x70,
0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03,
0x52, 0x0c, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x1d,
0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x08, 0x20, 0x01,
0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x52, 0x6f, 0x77, 0x73, 0x22, 0xc8, 0x03,
0x0a, 0x19, 0x47, 0x65, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72,
0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69,
0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x12, 0x3b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x25, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69,
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4a, 0x6f,
0x62, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a,
0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72,
0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73,
0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73,
0x73, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c,
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f,
0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12,
0x52, 0x0a, 0x0f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73,
0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75,
0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x50, 0x72, 0x6f, 0x67, 0x72,
0x65, 0x73, 0x73, 0x52, 0x0e, 0x74, 0x61, 0x73, 0x6b, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73,
0x73, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f,
0x72, 0x6f, 0x77, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x69, 0x6d, 0x70, 0x6f,
0x72, 0x74, 0x65, 0x64, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61,
0x6c, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f,
0x74, 0x61, 0x6c, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74,
0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x72,
0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x54, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74,
0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e,
0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x62, 0x49, 0x44, 0x18, 0x01,
0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x64, 0x62, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f,
0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x56,
0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a,
0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x86, 0x02, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x49,
0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33,
0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b,
0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f,
0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61,
0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x73, 0x18, 0x02, 0x20,
0x03, 0x28, 0x09, 0x52, 0x06, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x73, 0x12, 0x3d, 0x0a, 0x06, 0x73,
0x74, 0x61, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x6d, 0x69,
0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61,
0x74, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65,
0x61, 0x73, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x61,
0x73, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73,
0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65,
0x73, 0x73, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f,
0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22,
0x74, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x49, 0x6e,
0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x62, 0x4e,
0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d,
0x65, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49,
0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74,
0x49, 0x44, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65,
0x6e, 0x74, 0x49, 0x44, 0x73, 0x22, 0x3f, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x69,
0x6e, 0x6c, 0x6f, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x49, 0x44, 0x18,
0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x16,
0x0a, 0x06, 0x6c, 0x6f, 0x67, 0x49, 0x44, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x06,
0x6c, 0x6f, 0x67, 0x49, 0x44, 0x73, 0x22, 0x82, 0x04, 0x0a, 0x0b, 0x53, 0x65, 0x67, 0x6d, 0x65,
0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e,
0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x65, 0x67, 0x6d, 0x65,
0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c,
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x74,
0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x70,
0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x43,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x76, 0x43,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x75, 0x6d, 0x5f, 0x72, 0x6f,
0x77, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77,
0x73, 0x12, 0x37, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e,
0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74,
0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x6c, 0x65,
0x76, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76,
0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65,
0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x73, 0x6f, 0x72, 0x74, 0x65, 0x64,
0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x53, 0x6f, 0x72, 0x74, 0x65, 0x64,
0x12, 0x43, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x18,
0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x46, 0x69,
0x65, 0x6c, 0x64, 0x42, 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x65, 0x72,
0x74, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x41, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x6c,
0x6f, 0x67, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6d, 0x69, 0x6c, 0x76,
0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
0x6c, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x52, 0x09, 0x64,
0x65, 0x6c, 0x74, 0x61, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x41, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74,
0x73, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6d,
0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x69, 0x6e, 0x6c, 0x6f, 0x67,
0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x73, 0x4c, 0x6f, 0x67, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x17,
0x47, 0x65, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x0c,
0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65,
0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0c, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49,
0x6e, 0x66, 0x6f, 0x73, 0x22, 0x4a, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61,
0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30,
0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d,
0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x6f, 0x6e, 0x2e, 0x4d, 0x73, 0x67, 0x42, 0x61, 0x73, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65,
0x22, 0x71, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x4d, 0x65, 0x74, 0x72,
0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69,
0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x69, 0x6e, 0x66, 0x6f,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x49,
0x6e, 0x66, 0x6f, 0x22, 0x6d, 0x0a, 0x10, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70,
0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12,
0x21, 0x0a, 0x0c, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4e, 0x61,
0x6d, 0x65, 0x22, 0x7a, 0x0a, 0x17, 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x45, 0x0a,
0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x27, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x5d,
0x0a, 0x10, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x7a, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x30, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1c, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x73, 0x67, 0x42, 0x61, 0x73, 0x65, 0x52, 0x04,
0x62, 0x61, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x5a, 0x0a,
0x11, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x7a, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
0x6a, 0x6f, 0x62, 0x49, 0x44, 0x22, 0x49, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x49, 0x6d, 0x70, 0x6f,
0x72, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f,
0x62, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44,
0x22, 0x81, 0x02, 0x0a, 0x12, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x50,
0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65,
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a,
0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f,
0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x72, 0x6f,
0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74,
0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f,
0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74,
0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65,
0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x77,
0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65,
0x64, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x72,
0x6f, 0x77, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c,
0x52, 0x6f, 0x77, 0x73, 0x22, 0xc8, 0x03, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x49, 0x6d, 0x70, 0x6f,
0x72, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x7a, 0x6b, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x7a, 0x6b, 0x2a, 0x59, 0x0a, 0x0a, 0x53, 0x65, 0x61,
0x72, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x46, 0x41, 0x55,
0x4c, 0x54, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x55, 0x52, 0x45, 0x5f, 0x41, 0x4e, 0x4e,
0x5f, 0x53, 0x45, 0x41, 0x52, 0x43, 0x48, 0x5f, 0x4e, 0x4f, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45,
0x52, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b, 0x50, 0x55, 0x52, 0x45, 0x5f, 0x41, 0x4e, 0x4e, 0x5f,
0x53, 0x45, 0x41, 0x52, 0x43, 0x48, 0x5f, 0x57, 0x49, 0x54, 0x48, 0x5f, 0x46, 0x49, 0x4c, 0x54,
0x45, 0x52, 0x10, 0x02, 0x2a, 0x45, 0x0a, 0x09, 0x52, 0x61, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70,
0x65, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x10, 0x00, 0x12, 0x0c,
0x0a, 0x08, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a,
0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09,
0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x03, 0x2a, 0xc8, 0x01, 0x0a, 0x08,
0x52, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x44, 0x44, 0x4c, 0x43,
0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x44,
0x44, 0x4c, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x01, 0x12, 0x0c, 0x0a,
0x08, 0x44, 0x44, 0x4c, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x44,
0x44, 0x4c, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x44, 0x44, 0x4c,
0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09,
0x44, 0x4d, 0x4c, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x44,
0x4d, 0x4c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x10, 0x06, 0x12, 0x0f, 0x0a, 0x0b, 0x44, 0x4d,
0x4c, 0x42, 0x75, 0x6c, 0x6b, 0x4c, 0x6f, 0x61, 0x64, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x44,
0x51, 0x4c, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x10, 0x08, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x51,
0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x10, 0x09, 0x12, 0x11, 0x0a, 0x09, 0x44, 0x4d, 0x4c, 0x55,
0x70, 0x73, 0x65, 0x72, 0x74, 0x10, 0x0a, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x44,
0x44, 0x4c, 0x44, 0x42, 0x10, 0x0b, 0x2a, 0xa4, 0x01, 0x0a, 0x0e, 0x49, 0x6d, 0x70, 0x6f, 0x72,
0x74, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x6e,
0x65, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x10, 0x01,
0x12, 0x10, 0x0a, 0x0c, 0x50, 0x72, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67,
0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x10,
0x03, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x04, 0x12, 0x0d, 0x0a,
0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d,
0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x10, 0x06, 0x12,
0x0b, 0x0a, 0x07, 0x53, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x10, 0x07, 0x12, 0x0f, 0x0a, 0x0b,
0x55, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x10, 0x08, 0x12, 0x0e, 0x0a,
0x0a, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x10, 0x09, 0x42, 0x35, 0x5a,
0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x69, 0x6c, 0x76,
0x75, 0x73, 0x2d, 0x69, 0x6f, 0x2f, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2f, 0x70, 0x6b, 0x67,
0x2f, 0x76, 0x33, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
0x61, 0x6c, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x49,
0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73,
0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08,
0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08,
0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, 0x6c,
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d,
0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x74, 0x69,
0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65,
0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x52, 0x0a, 0x0f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x70,
0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x29, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69,
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61,
0x73, 0x6b, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0e, 0x74, 0x61, 0x73, 0x6b,
0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6d,
0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28,
0x03, 0x52, 0x0c, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x52, 0x6f, 0x77, 0x73, 0x12,
0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x09, 0x20,
0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x1f,
0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22,
0x54, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x12, 0x0a,
0x04, 0x64, 0x62, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x64, 0x62, 0x49,
0x44, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49,
0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x56, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, 0x70,
0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64,
0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x62,
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63,
0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x86, 0x02,
0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74,
0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6a, 0x6f,
0x62, 0x49, 0x44, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x6a, 0x6f, 0x62, 0x49,
0x44, 0x73, 0x12, 0x3d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03,
0x28, 0x0e, 0x32, 0x25, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72,
0x74, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x65,
0x73, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03,
0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x70,
0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x03, 0x52,
0x0a, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x63,
0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18,
0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x74, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x53, 0x65, 0x67,
0x6d, 0x65, 0x6e, 0x74, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x16, 0x0a, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c,
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c,
0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a,
0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03,
0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x73, 0x22, 0x3f, 0x0a, 0x0b,
0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x66,
0x69, 0x65, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x69,
0x65, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x67, 0x49, 0x44, 0x73, 0x18,
0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x06, 0x6c, 0x6f, 0x67, 0x49, 0x44, 0x73, 0x22, 0x82, 0x04,
0x0a, 0x0b, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1c, 0x0a,
0x09, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
0x52, 0x09, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x63,
0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28,
0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12,
0x20, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x03,
0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49,
0x44, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x76, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x0a,
0x08, 0x6e, 0x75, 0x6d, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52,
0x07, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x37, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74,
0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65,
0x67, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74,
0x65, 0x12, 0x37, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e,
0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4c, 0x65,
0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73,
0x5f, 0x73, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69,
0x73, 0x53, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x65, 0x72,
0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6d,
0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x69, 0x6e, 0x6c, 0x6f, 0x67,
0x52, 0x0a, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x41, 0x0a, 0x0a,
0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x22, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x69,
0x6e, 0x6c, 0x6f, 0x67, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x4c, 0x6f, 0x67, 0x73, 0x12,
0x41, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0b, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x46, 0x69, 0x65, 0x6c,
0x64, 0x42, 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x73, 0x4c, 0x6f,
0x67, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e,
0x74, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33,
0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b,
0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f,
0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61,
0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x0c, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e,
0x66, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6d, 0x69, 0x6c, 0x76,
0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
0x6c, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0c, 0x73,
0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x73, 0x22, 0x4a, 0x0a, 0x16, 0x47,
0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x73, 0x67, 0x42, 0x61, 0x73,
0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x22, 0x71, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x51, 0x75,
0x6f, 0x74, 0x61, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x74, 0x72, 0x69,
0x63, 0x73, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d,
0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x6d, 0x0a, 0x10, 0x46, 0x69,
0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12,
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01,
0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67,
0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x74,
0x6f, 0x72, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x7a, 0x0a, 0x17, 0x53, 0x79, 0x6e,
0x63, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x45, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e,
0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f,
0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x5d, 0x0a, 0x10, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45,
0x7a, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x04, 0x62, 0x61, 0x73,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x73,
0x67, 0x42, 0x61, 0x73, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64,
0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x62,
0x4e, 0x61, 0x6d, 0x65, 0x22, 0x5a, 0x0a, 0x11, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x7a,
0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61,
0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76,
0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x10,
0x0a, 0x03, 0x65, 0x7a, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x7a, 0x6b,
0x2a, 0x59, 0x0a, 0x0a, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b,
0x0a, 0x07, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x50,
0x55, 0x52, 0x45, 0x5f, 0x41, 0x4e, 0x4e, 0x5f, 0x53, 0x45, 0x41, 0x52, 0x43, 0x48, 0x5f, 0x4e,
0x4f, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b, 0x50, 0x55,
0x52, 0x45, 0x5f, 0x41, 0x4e, 0x4e, 0x5f, 0x53, 0x45, 0x41, 0x52, 0x43, 0x48, 0x5f, 0x57, 0x49,
0x54, 0x48, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x10, 0x02, 0x2a, 0x45, 0x0a, 0x09, 0x52,
0x61, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x6c, 0x75, 0x73,
0x74, 0x65, 0x72, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73,
0x65, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e,
0x10, 0x03, 0x2a, 0xc8, 0x01, 0x0a, 0x08, 0x52, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12,
0x11, 0x0a, 0x0d, 0x44, 0x44, 0x4c, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x44, 0x4c, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69,
0x6f, 0x6e, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x44, 0x4c, 0x49, 0x6e, 0x64, 0x65, 0x78,
0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x44, 0x4c, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x10, 0x03,
0x12, 0x11, 0x0a, 0x0d, 0x44, 0x44, 0x4c, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x44, 0x4d, 0x4c, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74,
0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x44, 0x4d, 0x4c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x10,
0x06, 0x12, 0x0f, 0x0a, 0x0b, 0x44, 0x4d, 0x4c, 0x42, 0x75, 0x6c, 0x6b, 0x4c, 0x6f, 0x61, 0x64,
0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x44, 0x51, 0x4c, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x10,
0x08, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x10, 0x09, 0x12,
0x11, 0x0a, 0x09, 0x44, 0x4d, 0x4c, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x10, 0x0a, 0x1a, 0x02,
0x08, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x44, 0x4c, 0x44, 0x42, 0x10, 0x0b, 0x2a, 0xa4, 0x01,
0x0a, 0x0e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x65,
0x12, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x65,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x72, 0x65, 0x49, 0x6d,
0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x6d, 0x70,
0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x61, 0x69, 0x6c,
0x65, 0x64, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65,
0x64, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x75, 0x69, 0x6c,
0x64, 0x69, 0x6e, 0x67, 0x10, 0x06, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x6f, 0x72, 0x74, 0x69, 0x6e,
0x67, 0x10, 0x07, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74,
0x65, 0x64, 0x10, 0x08, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x69,
0x6e, 0x67, 0x10, 0x09, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2d, 0x69, 0x6f, 0x2f, 0x6d, 0x69, 0x6c,
0x76, 0x75, 0x73, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
var (
@@ -6434,6 +6446,7 @@ func file_internal_proto_init() {
}
}
}
file_internal_proto_msgTypes[28].OneofWrappers = []interface{}{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
+9
View File
@@ -2053,6 +2053,7 @@ type proxyConfig struct {
MaxNameLength ParamItem `refreshable:"true"`
MaxCollectionDescriptionLength ParamItem `refreshable:"true"`
MaxUsernameLength ParamItem `refreshable:"true"`
MaxUserDescriptionLength ParamItem `refreshable:"true"`
MinPasswordLength ParamItem `refreshable:"true"`
MaxPasswordLength ParamItem `refreshable:"true"`
MaxFieldNum ParamItem `refreshable:"true"`
@@ -2175,6 +2176,14 @@ func (p *proxyConfig) init(base *BaseTable) {
}
p.MaxUsernameLength.Init(base.mgr)
p.MaxUserDescriptionLength = ParamItem{
Key: "proxy.maxUserDescriptionLength",
DefaultValue: "1024",
Version: "2.6.19",
PanicIfEmpty: true,
}
p.MaxUserDescriptionLength.Init(base.mgr)
p.MaxPasswordLength = ParamItem{
Key: "proxy.maxPasswordLength",
DefaultValue: "72", // bcrypt max length
@@ -236,6 +236,7 @@ func TestComponentParam(t *testing.T) {
t.Logf("MsgStreamTimeTickBufSize: %d", Params.MsgStreamTimeTickBufSize.GetAsInt64())
t.Logf("MaxNameLength: %d", Params.MaxNameLength.GetAsInt64())
assert.Equal(t, 1024, Params.MaxUserDescriptionLength.GetAsInt())
t.Logf("MaxFieldNum: %d", Params.MaxFieldNum.GetAsInt64())
+1 -1
View File
@@ -5,7 +5,7 @@ go 1.26.4
require (
github.com/apache/arrow/go/v17 v17.0.0
github.com/cockroachdb/errors v1.9.1
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260526093827-489331a5a41f
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260612100615-43795e8f8f6e
github.com/milvus-io/milvus/client/v2 v2.0.0-20241125024034-0b9edb62a92d
github.com/milvus-io/milvus/pkg/v3 v3.0.0-beta
github.com/minio/minio-go/v7 v7.0.73
+2 -2
View File
@@ -245,8 +245,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260526093827-489331a5a41f h1:W+paUzc+AA381Rydx2DJffoQuM4l+efVqO+2i9GdfMw=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260526093827-489331a5a41f/go.mod h1:rbKpv5JToISTKTTLl0duL5r6wbYnjJ9SsD0QgXMzKy0=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260612100615-43795e8f8f6e h1:axPnOhjsWqqxk0dOQRSIU2mdg52Kyl/8E6iDFZy/IpE=
github.com/milvus-io/milvus-proto/go-api/v3 v3.0.0-20260612100615-43795e8f8f6e/go.mod h1:rbKpv5JToISTKTTLl0duL5r6wbYnjJ9SsD0QgXMzKy0=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=
+71
View File
@@ -26,6 +26,7 @@ import (
"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
"github.com/milvus-io/milvus-proto/go-api/v3/milvuspb"
"github.com/milvus-io/milvus/pkg/v3/util"
"github.com/milvus-io/milvus/pkg/v3/util/crypto"
"github.com/milvus-io/milvus/pkg/v3/util/merr"
"github.com/milvus-io/milvus/pkg/v3/util/paramtable"
"github.com/milvus-io/milvus/tests/integration"
@@ -107,6 +108,76 @@ func (s *RBACBasicTestSuite) TestDropRole() {
}
}
func (s *RBACBasicTestSuite) TestCredentialDescriptionRoundTrip() {
rootCtx := GetContext(context.Background(), defaultAuth)
userName := fmt.Sprintf("desc_user_%d", rand.Int31n(1000000))
oldPassword := "old_password"
newPassword := "new_password"
description := "CJK user description \xe6\x9d\x83\xe9\x99\x90\xe7\x94\xa8\xe6\x88\xb7"
updatedDescription := "updated description \xe6\x9b\xb4\xe6\x96\xb0"
resp, err := s.Cluster.MilvusClient.CreateCredential(rootCtx, &milvuspb.CreateCredentialRequest{
Username: userName,
Password: crypto.Base64Encode(oldPassword),
Description: &description,
})
s.NoError(err)
s.True(merr.Ok(resp))
defer s.Cluster.MilvusClient.DeleteCredential(rootCtx, &milvuspb.DeleteCredentialRequest{Username: userName}) //nolint
selectResp, err := s.Cluster.MilvusClient.SelectUser(rootCtx, &milvuspb.SelectUserRequest{
User: &milvuspb.UserEntity{Name: userName},
IncludeRoleInfo: false,
})
s.NoError(err)
s.True(merr.Ok(selectResp.GetStatus()))
s.Len(selectResp.GetResults(), 1)
s.Equal(description, selectResp.GetResults()[0].GetDescription())
updateResp, err := s.Cluster.MilvusClient.UpdateCredential(rootCtx, &milvuspb.UpdateCredentialRequest{
Username: userName,
Description: &updatedDescription,
})
s.NoError(err)
s.True(merr.Ok(updateResp))
oldUserCtx := GetContext(context.Background(), fmt.Sprintf("%s:%s", userName, oldPassword))
authResp, err := s.Cluster.MilvusClient.ShowCollections(oldUserCtx, &milvuspb.ShowCollectionsRequest{})
s.NoError(err)
s.True(merr.Ok(authResp.GetStatus()))
selectResp, err = s.Cluster.MilvusClient.SelectUser(rootCtx, &milvuspb.SelectUserRequest{
User: &milvuspb.UserEntity{Name: userName},
IncludeRoleInfo: false,
})
s.NoError(err)
s.True(merr.Ok(selectResp.GetStatus()))
s.Len(selectResp.GetResults(), 1)
s.Equal(updatedDescription, selectResp.GetResults()[0].GetDescription())
updateResp, err = s.Cluster.MilvusClient.UpdateCredential(rootCtx, &milvuspb.UpdateCredentialRequest{
Username: userName,
OldPassword: crypto.Base64Encode(oldPassword),
NewPassword: crypto.Base64Encode(newPassword),
})
s.NoError(err)
s.True(merr.Ok(updateResp))
newUserCtx := GetContext(context.Background(), fmt.Sprintf("%s:%s", userName, newPassword))
authResp, err = s.Cluster.MilvusClient.ShowCollections(newUserCtx, &milvuspb.ShowCollectionsRequest{})
s.NoError(err)
s.True(merr.Ok(authResp.GetStatus()))
selectResp, err = s.Cluster.MilvusClient.SelectUser(rootCtx, &milvuspb.SelectUserRequest{
User: &milvuspb.UserEntity{Name: userName},
IncludeRoleInfo: false,
})
s.NoError(err)
s.True(merr.Ok(selectResp.GetStatus()))
s.Len(selectResp.GetResults(), 1)
s.Equal(updatedDescription, selectResp.GetResults()[0].GetDescription())
}
func TestRBAC(t *testing.T) {
suite.Run(t, new(RBACBasicTestSuite))
}