From 7c4d6d984dcd1ef662218b03579db8375b4c7562 Mon Sep 17 00:00:00 2001 From: Rohil Surana Date: Tue, 30 Jun 2026 16:54:47 +0530 Subject: [PATCH 01/10] feat: relation-level platform-user grant/revoke + audit + relation-selective removal --- Makefile | 2 +- cmd/serve.go | 4 +- .../mocks/audit_record_repository.go | 94 + core/serviceuser/service.go | 116 +- core/serviceuser/service_test.go | 130 +- core/user/mocks/audit_record_repository.go | 94 + core/user/service.go | 112 +- core/user/service_test.go | 301 +- internal/api/v1beta1connect/interfaces.go | 4 +- .../mocks/service_user_service.go | 21 +- .../api/v1beta1connect/mocks/user_service.go | 24 +- internal/api/v1beta1connect/platform.go | 101 +- internal/api/v1beta1connect/platform_test.go | 106 + pkg/auditrecord/consts.go | 7 + proto/v1beta1/admin.pb.go | 2690 +++++++++-------- 15 files changed, 2237 insertions(+), 1569 deletions(-) create mode 100644 core/serviceuser/mocks/audit_record_repository.go create mode 100644 core/user/mocks/audit_record_repository.go create mode 100644 internal/api/v1beta1connect/platform_test.go diff --git a/Makefile b/Makefile index 6e4ebebb0..ce0aef8fa 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ TAG := $(shell git rev-list --tags --max-count=1) VERSION := $(shell git describe --tags ${TAG}) .PHONY: build check fmt lint test test-race vet test-cover-html help install proto admin-app compose-up-dev .DEFAULT_GOAL := build -PROTON_COMMIT := "795c70f359264a3c21a4c6412097f139dfc387e6" +PROTON_COMMIT := "ee05c27600bd7d2782da4fde0997f84aa69e7eeb" admin-app: @echo " > generating admin build" diff --git a/cmd/serve.go b/cmd/serve.go index 2280d7995..4982a6dcb 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -395,7 +395,7 @@ func buildAPIDependencies( svUserRepo := postgres.NewServiceUserRepository(dbc) scUserCredRepo := postgres.NewServiceUserCredentialRepository(dbc) - serviceUserService := serviceuser.NewService(logger, svUserRepo, scUserCredRepo, relationService) + serviceUserService := serviceuser.NewService(logger, svUserRepo, scUserCredRepo, relationService, auditRecordRepository) var mailDialer mailer.Dialer = mailer.NewMockDialer() if cfg.App.Mailer.SMTPHost != "" && cfg.App.Mailer.SMTPHost != "smtp.example.com" { @@ -432,7 +432,7 @@ func buildAPIDependencies( // back here because role.Service depends on permission.Service permissionService.SetRoleService(roleService) policyService := policy.NewService(policyPGRepository, relationService, roleService) - userService := user.NewService(userRepository, relationService, sessionService) + userService := user.NewService(userRepository, relationService, sessionService, auditRecordRepository) patValidator := userpat.NewValidator(logger, userPATRepo, cfg.App.PAT) authnService := authenticate.NewService(logger, cfg.App.Authentication, postgres.NewFlowRepository(logger, dbc), mailDialer, tokenService, sessionService, userService, serviceUserService, webAuthConfig, patValidator) diff --git a/core/serviceuser/mocks/audit_record_repository.go b/core/serviceuser/mocks/audit_record_repository.go new file mode 100644 index 000000000..87c61b8a3 --- /dev/null +++ b/core/serviceuser/mocks/audit_record_repository.go @@ -0,0 +1,94 @@ +// Code generated by mockery v2.53.5. DO NOT EDIT. + +package mocks + +import ( + context "context" + + models "github.com/raystack/frontier/core/auditrecord/models" + mock "github.com/stretchr/testify/mock" +) + +// AuditRecordRepository is an autogenerated mock type for the AuditRecordRepository type +type AuditRecordRepository struct { + mock.Mock +} + +type AuditRecordRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *AuditRecordRepository) EXPECT() *AuditRecordRepository_Expecter { + return &AuditRecordRepository_Expecter{mock: &_m.Mock} +} + +// Create provides a mock function with given fields: ctx, auditRecord +func (_m *AuditRecordRepository) Create(ctx context.Context, auditRecord models.AuditRecord) (models.AuditRecord, error) { + ret := _m.Called(ctx, auditRecord) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 models.AuditRecord + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, models.AuditRecord) (models.AuditRecord, error)); ok { + return rf(ctx, auditRecord) + } + if rf, ok := ret.Get(0).(func(context.Context, models.AuditRecord) models.AuditRecord); ok { + r0 = rf(ctx, auditRecord) + } else { + r0 = ret.Get(0).(models.AuditRecord) + } + + if rf, ok := ret.Get(1).(func(context.Context, models.AuditRecord) error); ok { + r1 = rf(ctx, auditRecord) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AuditRecordRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type AuditRecordRepository_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - ctx context.Context +// - auditRecord models.AuditRecord +func (_e *AuditRecordRepository_Expecter) Create(ctx interface{}, auditRecord interface{}) *AuditRecordRepository_Create_Call { + return &AuditRecordRepository_Create_Call{Call: _e.mock.On("Create", ctx, auditRecord)} +} + +func (_c *AuditRecordRepository_Create_Call) Run(run func(ctx context.Context, auditRecord models.AuditRecord)) *AuditRecordRepository_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(models.AuditRecord)) + }) + return _c +} + +func (_c *AuditRecordRepository_Create_Call) Return(_a0 models.AuditRecord, _a1 error) *AuditRecordRepository_Create_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AuditRecordRepository_Create_Call) RunAndReturn(run func(context.Context, models.AuditRecord) (models.AuditRecord, error)) *AuditRecordRepository_Create_Call { + _c.Call.Return(run) + return _c +} + +// NewAuditRecordRepository creates a new instance of AuditRecordRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewAuditRecordRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *AuditRecordRepository { + mock := &AuditRecordRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/serviceuser/service.go b/core/serviceuser/service.go index f9a8f8df6..fb05f1e71 100644 --- a/core/serviceuser/service.go +++ b/core/serviceuser/service.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "log/slog" + "time" "golang.org/x/crypto/sha3" @@ -18,8 +19,10 @@ import ( "github.com/google/uuid" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/raystack/frontier/core/auditrecord/models" "github.com/raystack/frontier/core/relation" "github.com/raystack/frontier/internal/bootstrap/schema" + pkgAuditRecord "github.com/raystack/frontier/pkg/auditrecord" "github.com/raystack/frontier/pkg/utils" ) @@ -51,20 +54,26 @@ type MembershipService interface { ListPrincipalIDsByResource(ctx context.Context, resourceID, resourceType, principalType string) ([]string, error) } +type AuditRecordRepository interface { + Create(ctx context.Context, auditRecord models.AuditRecord) (models.AuditRecord, error) +} + type Service struct { - log *slog.Logger - repo Repository - credRepo CredentialRepository - relationService RelationService - membershipService MembershipService + log *slog.Logger + repo Repository + credRepo CredentialRepository + relationService RelationService + membershipService MembershipService + auditRecordRepository AuditRecordRepository } -func NewService(logger *slog.Logger, repo Repository, credRepo CredentialRepository, relService RelationService) *Service { +func NewService(logger *slog.Logger, repo Repository, credRepo CredentialRepository, relService RelationService, auditRecordRepository AuditRecordRepository) *Service { return &Service{ - log: logger, - repo: repo, - credRepo: credRepo, - relationService: relService, + log: logger, + repo: repo, + credRepo: credRepo, + relationService: relService, + auditRecordRepository: auditRecordRepository, } } @@ -437,19 +446,19 @@ func (s Service) Sudo(ctx context.Context, id string, relationName string) error return err } - // check if already su - permissionName := "" + // validate the requested platform relation switch relationName { - case schema.MemberRelationName: - permissionName = schema.PlatformCheckPermission - case schema.AdminRelationName: - permissionName = schema.PlatformSudoPermission - } - if permissionName == "" { + case schema.MemberRelationName, schema.AdminRelationName: + default: return fmt.Errorf("invalid relation name, possible options are: %s, %s", schema.MemberRelationName, schema.AdminRelationName) } - if ok, err := s.IsSudo(ctx, currentUser.ID, permissionName); err != nil { + // Idempotent on the exact relation tuple, not the derived permission: + // `check` is granted by both admin and member, so a permission check would + // skip creating the member tuple for an existing admin. That breaks + // downgrade (Sudo member then UnSudo admin), which UnSudo evaluates at the + // relation level — keep the two symmetric. + if ok, err := s.IsSudo(ctx, currentUser.ID, relationName); err != nil { return err } else if ok { return nil @@ -467,29 +476,46 @@ func (s Service) Sudo(ctx context.Context, id string, relationName string) error }, RelationName: relationName, }) - return err + if err != nil { + return err + } + + // audit the grant for both admin and member relations + event := pkgAuditRecord.PlatformAdminAddedEvent + if relationName == schema.MemberRelationName { + event = pkgAuditRecord.PlatformMemberAddedEvent + } + return s.recordPlatformAuditRecord(ctx, currentUser, event, relationName) } -// UnSudo remove platform permissions to user -// only remove the 'member' relation if it exists -func (s Service) UnSudo(ctx context.Context, id string) error { +// UnSudo removes a platform relation (admin or member) from a service user. +// It removes the exact relation requested — an `admin` relation can now actually +// be stripped. Both admin and member grants/removals are audited. +func (s Service) UnSudo(ctx context.Context, id, relationName string) error { + switch relationName { + case schema.AdminRelationName, schema.MemberRelationName: + default: + return fmt.Errorf("invalid relation name, possible options are: %s, %s", schema.MemberRelationName, schema.AdminRelationName) + } + currentUser, err := s.Get(ctx, id) if err != nil { return err } - relationName := schema.MemberRelationName - // to check if the user has member relation, we need to check if the user has `check` - // permission on platform - if ok, err := s.IsSudo(ctx, currentUser.ID, schema.PlatformCheckPermission); err != nil { + // Only act (and audit) when the specific relation actually exists, so the + // revoke event reflects a real state change. Checking the relation directly + // is precise for both admin and member. + present, err := s.IsSudo(ctx, currentUser.ID, relationName) + if err != nil { return err - } else if !ok { - // not needed + } + if !present { return nil } // unmark su - err = s.relationService.Delete(ctx, relation.Relation{ + if err := s.relationService.Delete(ctx, relation.Relation{ Object: relation.Object{ ID: schema.PlatformID, Namespace: schema.PlatformNamespace, @@ -499,6 +525,36 @@ func (s Service) UnSudo(ctx context.Context, id string) error { Namespace: schema.ServiceUserPrincipal, }, RelationName: relationName, + }); err != nil { + return err + } + + event := pkgAuditRecord.PlatformAdminRemovedEvent + if relationName == schema.MemberRelationName { + event = pkgAuditRecord.PlatformMemberRemovedEvent + } + return s.recordPlatformAuditRecord(ctx, currentUser, event, relationName) +} + +// recordPlatformAuditRecord writes an audit record for a platform relation +// (admin or member) grant/revoke on a service user. Actor is left empty so the +// repository enriches it from context (acting principal, or system actor at boot). +func (s Service) recordPlatformAuditRecord(ctx context.Context, su ServiceUser, event pkgAuditRecord.Event, relationName string) error { + _, err := s.auditRecordRepository.Create(ctx, models.AuditRecord{ + Event: event, + Resource: models.Resource{ + ID: schema.PlatformID, + Type: pkgAuditRecord.PlatformType, + Name: schema.PlatformID, + }, + Target: &models.Target{ + ID: su.ID, + Type: pkgAuditRecord.ServiceUserType, + Name: su.Title, + }, + OrgID: schema.PlatformOrgID.String(), + OccurredAt: time.Now().UTC(), + Metadata: map[string]any{"relation": relationName}, }) return err } diff --git a/core/serviceuser/service_test.go b/core/serviceuser/service_test.go index 8c295b0fd..c4ba9ac33 100644 --- a/core/serviceuser/service_test.go +++ b/core/serviceuser/service_test.go @@ -7,22 +7,140 @@ import ( "log/slog" "testing" + "github.com/raystack/frontier/core/auditrecord/models" "github.com/raystack/frontier/core/relation" "github.com/raystack/frontier/core/serviceuser" "github.com/raystack/frontier/core/serviceuser/mocks" "github.com/raystack/frontier/internal/bootstrap/schema" + pkgAuditRecord "github.com/raystack/frontier/pkg/auditrecord" + "github.com/stretchr/testify/mock" ) -func newTestService(t *testing.T) (*serviceuser.Service, *mocks.Repository, *mocks.CredentialRepository, *mocks.RelationService, *mocks.MembershipService) { +func newTestService(t *testing.T) (*serviceuser.Service, *mocks.Repository, *mocks.CredentialRepository, *mocks.RelationService, *mocks.MembershipService, *mocks.AuditRecordRepository) { t.Helper() repo := mocks.NewRepository(t) credRepo := mocks.NewCredentialRepository(t) relSvc := mocks.NewRelationService(t) memSvc := mocks.NewMembershipService(t) + auditRepo := mocks.NewAuditRecordRepository(t) logger := slog.New(slog.NewTextHandler(io.Discard, nil)) - svc := serviceuser.NewService(logger, repo, credRepo, relSvc) + svc := serviceuser.NewService(logger, repo, credRepo, relSvc, auditRepo) svc.SetMembershipService(memSvc) - return svc, repo, credRepo, relSvc, memSvc + return svc, repo, credRepo, relSvc, memSvc, auditRepo +} + +func TestService_Sudo(t *testing.T) { + ctx := context.Background() + const suID = "550e8400-e29b-41d4-a716-446655440000" + // platformRel builds the platform relation tuple used by IsSudo's check and by + // Create/Delete (field order in the literal is irrelevant for struct equality). + platformRel := func(rel string) relation.Relation { + return relation.Relation{ + Object: relation.Object{ID: schema.PlatformID, Namespace: schema.PlatformNamespace}, + Subject: relation.Subject{ID: suID, Namespace: schema.ServiceUserPrincipal}, + RelationName: rel, + } + } + + t.Run("grants admin relation and audits the grant", func(t *testing.T) { + svc, repo, _, rel, _, audit := newTestService(t) + repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, Title: "svc"}, nil) + // Sudo is idempotent on the exact relation (admin), not the superuser + // permission — symmetric with UnSudo. + rel.On("CheckPermission", ctx, platformRel(schema.AdminRelationName)).Return(false, nil) + rel.On("Create", ctx, platformRel(schema.AdminRelationName)).Return(relation.Relation{}, nil) + audit.On("Create", ctx, mock.MatchedBy(func(r models.AuditRecord) bool { + return r.Event == pkgAuditRecord.PlatformAdminAddedEvent && r.Target != nil && r.Target.ID == suID + })).Return(models.AuditRecord{}, nil) + + if err := svc.Sudo(ctx, suID, schema.AdminRelationName); err != nil { + t.Fatalf("Sudo() error = %v", err) + } + }) + + t.Run("grants member relation and audits the grant", func(t *testing.T) { + svc, repo, _, rel, _, audit := newTestService(t) + repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, Title: "svc"}, nil) + // member is checked at the relation level so an existing admin still gets + // the member tuple created (downgrade path). + rel.On("CheckPermission", ctx, platformRel(schema.MemberRelationName)).Return(false, nil) + rel.On("Create", ctx, platformRel(schema.MemberRelationName)).Return(relation.Relation{}, nil) + audit.On("Create", ctx, mock.MatchedBy(func(r models.AuditRecord) bool { + return r.Event == pkgAuditRecord.PlatformMemberAddedEvent && r.Target != nil && r.Target.ID == suID + })).Return(models.AuditRecord{}, nil) + + if err := svc.Sudo(ctx, suID, schema.MemberRelationName); err != nil { + t.Fatalf("Sudo() error = %v", err) + } + }) +} + +func TestService_UnSudo(t *testing.T) { + ctx := context.Background() + const suID = "550e8400-e29b-41d4-a716-446655440000" + platformRel := func(rel string) relation.Relation { + return relation.Relation{ + Object: relation.Object{ID: schema.PlatformID, Namespace: schema.PlatformNamespace}, + Subject: relation.Subject{ID: suID, Namespace: schema.ServiceUserPrincipal}, + RelationName: rel, + } + } + + t.Run("removes admin relation and audits the revoke", func(t *testing.T) { + svc, repo, _, rel, _, audit := newTestService(t) + repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, Title: "svc"}, nil) + // UnSudo checks the relation directly (admin), not the superuser permission + rel.On("CheckPermission", ctx, platformRel(schema.AdminRelationName)).Return(true, nil) + rel.On("Delete", ctx, platformRel(schema.AdminRelationName)).Return(nil) + audit.On("Create", ctx, mock.MatchedBy(func(r models.AuditRecord) bool { + return r.Event == pkgAuditRecord.PlatformAdminRemovedEvent && r.Target != nil && r.Target.ID == suID + })).Return(models.AuditRecord{}, nil) + + if err := svc.UnSudo(ctx, suID, schema.AdminRelationName); err != nil { + t.Fatalf("UnSudo() error = %v", err) + } + }) + + t.Run("admin removal is a no-op when the relation is absent", func(t *testing.T) { + svc, repo, _, rel, _, _ := newTestService(t) + repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, Title: "svc"}, nil) + rel.On("CheckPermission", ctx, platformRel(schema.AdminRelationName)).Return(false, nil) + + if err := svc.UnSudo(ctx, suID, schema.AdminRelationName); err != nil { + t.Fatalf("UnSudo() error = %v", err) + } + }) + + t.Run("removes member relation and audits the revoke", func(t *testing.T) { + svc, repo, _, rel, _, audit := newTestService(t) + repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, Title: "svc"}, nil) + rel.On("CheckPermission", ctx, platformRel(schema.MemberRelationName)).Return(true, nil) + rel.On("Delete", ctx, platformRel(schema.MemberRelationName)).Return(nil) + audit.On("Create", ctx, mock.MatchedBy(func(r models.AuditRecord) bool { + return r.Event == pkgAuditRecord.PlatformMemberRemovedEvent && r.Target != nil && r.Target.ID == suID + })).Return(models.AuditRecord{}, nil) + + if err := svc.UnSudo(ctx, suID, schema.MemberRelationName); err != nil { + t.Fatalf("UnSudo() error = %v", err) + } + }) + + t.Run("member removal is a no-op when the relation is absent", func(t *testing.T) { + svc, repo, _, rel, _, _ := newTestService(t) + repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, Title: "svc"}, nil) + rel.On("CheckPermission", ctx, platformRel(schema.MemberRelationName)).Return(false, nil) + + if err := svc.UnSudo(ctx, suID, schema.MemberRelationName); err != nil { + t.Fatalf("UnSudo() error = %v", err) + } + }) + + t.Run("rejects an invalid relation name", func(t *testing.T) { + svc, _, _, _, _, _ := newTestService(t) + if err := svc.UnSudo(ctx, suID, "owner"); err == nil { + t.Fatal("UnSudo() expected error for invalid relation, got nil") + } + }) } func TestService_Delete(t *testing.T) { @@ -103,7 +221,7 @@ func TestService_Delete(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - svc, repo, cred, rel, mem := newTestService(t) + svc, repo, cred, rel, mem, _ := newTestService(t) tt.setup(repo, cred, rel, mem) err := svc.Delete(ctx, suID) @@ -147,7 +265,7 @@ func TestService_Get(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - svc, repo, _, _, _ := newTestService(t) + svc, repo, _, _, _, _ := newTestService(t) tt.setup(repo) _, err := svc.Get(ctx, tt.id) @@ -214,7 +332,7 @@ func TestService_ListByOrg(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - svc, repo, _, _, mem := newTestService(t) + svc, repo, _, _, mem, _ := newTestService(t) tt.setup(repo, mem) got, err := svc.ListByOrg(ctx, orgID) diff --git a/core/user/mocks/audit_record_repository.go b/core/user/mocks/audit_record_repository.go new file mode 100644 index 000000000..87c61b8a3 --- /dev/null +++ b/core/user/mocks/audit_record_repository.go @@ -0,0 +1,94 @@ +// Code generated by mockery v2.53.5. DO NOT EDIT. + +package mocks + +import ( + context "context" + + models "github.com/raystack/frontier/core/auditrecord/models" + mock "github.com/stretchr/testify/mock" +) + +// AuditRecordRepository is an autogenerated mock type for the AuditRecordRepository type +type AuditRecordRepository struct { + mock.Mock +} + +type AuditRecordRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *AuditRecordRepository) EXPECT() *AuditRecordRepository_Expecter { + return &AuditRecordRepository_Expecter{mock: &_m.Mock} +} + +// Create provides a mock function with given fields: ctx, auditRecord +func (_m *AuditRecordRepository) Create(ctx context.Context, auditRecord models.AuditRecord) (models.AuditRecord, error) { + ret := _m.Called(ctx, auditRecord) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 models.AuditRecord + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, models.AuditRecord) (models.AuditRecord, error)); ok { + return rf(ctx, auditRecord) + } + if rf, ok := ret.Get(0).(func(context.Context, models.AuditRecord) models.AuditRecord); ok { + r0 = rf(ctx, auditRecord) + } else { + r0 = ret.Get(0).(models.AuditRecord) + } + + if rf, ok := ret.Get(1).(func(context.Context, models.AuditRecord) error); ok { + r1 = rf(ctx, auditRecord) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AuditRecordRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type AuditRecordRepository_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - ctx context.Context +// - auditRecord models.AuditRecord +func (_e *AuditRecordRepository_Expecter) Create(ctx interface{}, auditRecord interface{}) *AuditRecordRepository_Create_Call { + return &AuditRecordRepository_Create_Call{Call: _e.mock.On("Create", ctx, auditRecord)} +} + +func (_c *AuditRecordRepository_Create_Call) Run(run func(ctx context.Context, auditRecord models.AuditRecord)) *AuditRecordRepository_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(models.AuditRecord)) + }) + return _c +} + +func (_c *AuditRecordRepository_Create_Call) Return(_a0 models.AuditRecord, _a1 error) *AuditRecordRepository_Create_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AuditRecordRepository_Create_Call) RunAndReturn(run func(context.Context, models.AuditRecord) (models.AuditRecord, error)) *AuditRecordRepository_Create_Call { + _c.Call.Return(run) + return _c +} + +// NewAuditRecordRepository creates a new instance of AuditRecordRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewAuditRecordRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *AuditRecordRepository { + mock := &AuditRecordRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/user/service.go b/core/user/service.go index 1f8ec0e85..eb69da35c 100644 --- a/core/user/service.go +++ b/core/user/service.go @@ -14,8 +14,10 @@ import ( "github.com/raystack/frontier/pkg/utils" + "github.com/raystack/frontier/core/auditrecord/models" "github.com/raystack/frontier/core/relation" "github.com/raystack/frontier/internal/bootstrap/schema" + pkgAuditRecord "github.com/raystack/frontier/pkg/auditrecord" "github.com/raystack/frontier/pkg/errors" "github.com/raystack/frontier/pkg/str" ) @@ -34,19 +36,25 @@ type SessionService interface { DeleteByUserID(ctx context.Context, userID string) error } +type AuditRecordRepository interface { + Create(ctx context.Context, auditRecord models.AuditRecord) (models.AuditRecord, error) +} + type Service struct { - repository Repository - relationService RelationService - sessionService SessionService - Now func() time.Time + repository Repository + relationService RelationService + sessionService SessionService + auditRecordRepository AuditRecordRepository + Now func() time.Time } func NewService(repository Repository, relationRepo RelationService, - sessionService SessionService) *Service { + sessionService SessionService, auditRecordRepository AuditRecordRepository) *Service { return &Service{ - repository: repository, - relationService: relationRepo, - sessionService: sessionService, + repository: repository, + relationService: relationRepo, + sessionService: sessionService, + auditRecordRepository: auditRecordRepository, Now: func() time.Time { return time.Now().UTC() }, @@ -173,19 +181,19 @@ func (s Service) Sudo(ctx context.Context, id string, relationName string) error return err } - // check if already su - permissionName := "" + // validate the requested platform relation switch relationName { - case schema.MemberRelationName: - permissionName = schema.PlatformCheckPermission - case schema.AdminRelationName: - permissionName = schema.PlatformSudoPermission - } - if permissionName == "" { + case schema.MemberRelationName, schema.AdminRelationName: + default: return fmt.Errorf("invalid relation name, possible options are: %s, %s", schema.MemberRelationName, schema.AdminRelationName) } - if ok, err := s.IsSudo(ctx, currentUser.ID, permissionName); err != nil { + // Idempotent on the exact relation tuple, not the derived permission: + // `check` is granted by both admin and member, so a permission check would + // skip creating the member tuple for an existing admin. That breaks + // downgrade (Sudo member then UnSudo admin), which UnSudo evaluates at the + // relation level — keep the two symmetric. + if ok, err := s.IsSudo(ctx, currentUser.ID, relationName); err != nil { return err } else if ok { return nil @@ -203,29 +211,46 @@ func (s Service) Sudo(ctx context.Context, id string, relationName string) error }, RelationName: relationName, }) - return err + if err != nil { + return err + } + + // audit the grant for both admin and member relations + event := pkgAuditRecord.PlatformAdminAddedEvent + if relationName == schema.MemberRelationName { + event = pkgAuditRecord.PlatformMemberAddedEvent + } + return s.recordPlatformAuditRecord(ctx, currentUser, event, relationName) } -// UnSudo remove platform permissions to user -// only remove the 'member' relation if it exists -func (s Service) UnSudo(ctx context.Context, id string) error { +// UnSudo removes a platform relation (admin or member) from a user. +// It removes the exact relation requested — an `admin` relation can now actually +// be stripped. Both admin and member grants/removals are audited. +func (s Service) UnSudo(ctx context.Context, id, relationName string) error { + switch relationName { + case schema.AdminRelationName, schema.MemberRelationName: + default: + return fmt.Errorf("invalid relation name, possible options are: %s, %s", schema.MemberRelationName, schema.AdminRelationName) + } + currentUser, err := s.GetByID(ctx, id) if err != nil { return err } - relationName := schema.MemberRelationName - // to check if the user has member relation, we need to check if the user has `check` - // permission on platform - if ok, err := s.IsSudo(ctx, currentUser.ID, schema.PlatformCheckPermission); err != nil { + // Only act (and audit) when the specific relation actually exists, so the + // revoke event reflects a real state change. Checking the relation directly + // is precise for both admin and member. + present, err := s.IsSudo(ctx, currentUser.ID, relationName) + if err != nil { return err - } else if !ok { - // not needed + } + if !present { return nil } // unmark su - err = s.relationService.Delete(ctx, relation.Relation{ + if err := s.relationService.Delete(ctx, relation.Relation{ Object: relation.Object{ ID: schema.PlatformID, Namespace: schema.PlatformNamespace, @@ -235,6 +260,37 @@ func (s Service) UnSudo(ctx context.Context, id string) error { Namespace: schema.UserPrincipal, }, RelationName: relationName, + }); err != nil { + return err + } + + event := pkgAuditRecord.PlatformAdminRemovedEvent + if relationName == schema.MemberRelationName { + event = pkgAuditRecord.PlatformMemberRemovedEvent + } + return s.recordPlatformAuditRecord(ctx, currentUser, event, relationName) +} + +// recordPlatformAuditRecord writes an audit record for a platform relation +// (admin or member) grant/revoke. Actor is left empty so the repository enriches +// it from context — the acting principal on request paths, or the system actor +// (uuid.Nil) for boot-time/config-driven changes. +func (s Service) recordPlatformAuditRecord(ctx context.Context, u User, event pkgAuditRecord.Event, relationName string) error { + _, err := s.auditRecordRepository.Create(ctx, models.AuditRecord{ + Event: event, + Resource: models.Resource{ + ID: schema.PlatformID, + Type: pkgAuditRecord.PlatformType, + Name: schema.PlatformID, + }, + Target: &models.Target{ + ID: u.ID, + Type: pkgAuditRecord.UserType, + Name: u.Name, + }, + OrgID: schema.PlatformOrgID.String(), + OccurredAt: s.Now(), + Metadata: map[string]any{"relation": relationName}, }) return err } diff --git a/core/user/service_test.go b/core/user/service_test.go index 1b4e451d9..71beb277e 100644 --- a/core/user/service_test.go +++ b/core/user/service_test.go @@ -8,21 +8,24 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/uuid" + "github.com/raystack/frontier/core/auditrecord/models" "github.com/raystack/frontier/core/relation" "github.com/raystack/frontier/core/user" "github.com/raystack/frontier/core/user/mocks" "github.com/raystack/frontier/internal/bootstrap/schema" + pkgAuditRecord "github.com/raystack/frontier/pkg/auditrecord" "github.com/raystack/frontier/pkg/str" "github.com/stretchr/testify/mock" ) -func mockService(t *testing.T) (*mocks.Repository, *mocks.RelationService, *mocks.SessionService) { +func mockService(t *testing.T) (*mocks.Repository, *mocks.RelationService, *mocks.SessionService, *mocks.AuditRecordRepository) { t.Helper() repo := mocks.NewRepository(t) relationService := mocks.NewRelationService(t) sessionService := mocks.NewSessionService(t) - return repo, relationService, sessionService + auditRecordRepository := mocks.NewAuditRecordRepository(t) + return repo, relationService, sessionService, auditRecordRepository } func TestService_GetByID(t *testing.T) { @@ -43,12 +46,12 @@ func TestService_GetByID(t *testing.T) { }, wantErr: false, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().GetByID(mock.Anything, testID.String()).Return(user.User{ ID: testID.String(), Name: "test", }, nil) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -61,13 +64,13 @@ func TestService_GetByID(t *testing.T) { }, wantErr: false, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().GetByEmail(mock.Anything, "test@test.com").Return(user.User{ ID: testID.String(), Name: "test", Email: "test@test.com", }, nil) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -79,12 +82,12 @@ func TestService_GetByID(t *testing.T) { }, wantErr: false, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().GetByName(mock.Anything, "test").Return(user.User{ ID: testID.String(), Name: "test", }, nil) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -93,9 +96,9 @@ func TestService_GetByID(t *testing.T) { want: user.User{}, wantErr: true, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().GetByName(mock.Anything, "invalid").Return(user.User{}, errors.New("not found")) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, } @@ -150,7 +153,7 @@ func TestService_Create(t *testing.T) { }, wantErr: false, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().Create(mock.Anything, user.User{ Name: "test", Email: "test@email.com", @@ -173,7 +176,7 @@ func TestService_Create(t *testing.T) { CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), }, nil) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -185,13 +188,13 @@ func TestService_Create(t *testing.T) { want: user.User{}, wantErr: true, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().Create(mock.Anything, user.User{ Name: "test ", Email: "test", State: user.Enabled, }).Return(user.User{}, errors.New("failed to create")) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, } @@ -235,7 +238,7 @@ func TestService_List(t *testing.T) { }, wantErr: false, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().List(mock.Anything, user.Filter{ State: user.Enabled, }).Return([]user.User{ @@ -248,7 +251,7 @@ func TestService_List(t *testing.T) { Name: "test-2", }, }, nil) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, } @@ -302,7 +305,7 @@ func TestService_Update(t *testing.T) { }, wantErr: false, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().UpdateByEmail(mock.Anything, user.User{ ID: "test@email.com", Name: "test", @@ -324,7 +327,7 @@ func TestService_Update(t *testing.T) { CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), }, nil) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -353,7 +356,7 @@ func TestService_Update(t *testing.T) { }, wantErr: false, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().UpdateByName(mock.Anything, user.User{ ID: "test", Name: "test", @@ -375,7 +378,7 @@ func TestService_Update(t *testing.T) { CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), }, nil) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -404,7 +407,7 @@ func TestService_Update(t *testing.T) { }, wantErr: false, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().UpdateByID(mock.Anything, user.User{ ID: testID.String(), Name: "test", @@ -426,7 +429,7 @@ func TestService_Update(t *testing.T) { CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), }, nil) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -438,12 +441,12 @@ func TestService_Update(t *testing.T) { want: user.User{}, wantErr: true, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().UpdateByName(mock.Anything, user.User{ Name: "test ", Email: "test", }).Return(user.User{}, errors.New("failed to update")) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, } @@ -474,10 +477,10 @@ func TestService_Disable(t *testing.T) { name: "disable user with valid uuid", id: validID, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().SetState(mock.Anything, validID, user.Disabled).Return(nil) sessionService.EXPECT().DeleteByUserID(mock.Anything, validID).Return(nil) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -485,8 +488,8 @@ func TestService_Disable(t *testing.T) { id: "not-a-uuid", wantErr: user.ErrInvalidID, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) - return user.NewService(repo, relationService, sessionService) + repo, relationService, sessionService, auditRecordRepository := mockService(t) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -494,8 +497,8 @@ func TestService_Disable(t *testing.T) { id: "", wantErr: user.ErrInvalidID, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) - return user.NewService(repo, relationService, sessionService) + repo, relationService, sessionService, auditRecordRepository := mockService(t) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -503,9 +506,9 @@ func TestService_Disable(t *testing.T) { id: validID, wantErr: user.ErrNotExist, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().SetState(mock.Anything, validID, user.Disabled).Return(user.ErrNotExist) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, } @@ -536,9 +539,9 @@ func TestService_Enable(t *testing.T) { name: "enable user with valid uuid", id: validID, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().SetState(mock.Anything, validID, user.Enabled).Return(nil) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -546,8 +549,8 @@ func TestService_Enable(t *testing.T) { id: "not-a-uuid", wantErr: user.ErrInvalidID, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) - return user.NewService(repo, relationService, sessionService) + repo, relationService, sessionService, auditRecordRepository := mockService(t) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -555,8 +558,8 @@ func TestService_Enable(t *testing.T) { id: "", wantErr: user.ErrInvalidID, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) - return user.NewService(repo, relationService, sessionService) + repo, relationService, sessionService, auditRecordRepository := mockService(t) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -564,9 +567,9 @@ func TestService_Enable(t *testing.T) { id: validID, wantErr: user.ErrNotExist, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().SetState(mock.Anything, validID, user.Enabled).Return(user.ErrNotExist) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, } @@ -597,13 +600,13 @@ func TestService_Delete(t *testing.T) { id: "test-id", wantErr: false, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) relationService.EXPECT().Delete(mock.Anything, relation.Relation{Subject: relation.Subject{ ID: "test-id", Namespace: schema.UserPrincipal, }}).Return(nil) repo.EXPECT().Delete(mock.Anything, "test-id").Return(nil) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -611,12 +614,12 @@ func TestService_Delete(t *testing.T) { id: "test-id", wantErr: true, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) relationService.EXPECT().Delete(mock.Anything, relation.Relation{Subject: relation.Subject{ ID: "test-id", Namespace: schema.UserPrincipal, }}).Return(errors.New("failed to delete relation")) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, } @@ -649,7 +652,7 @@ func TestService_Sudo(t *testing.T) { relationName: schema.AdminRelationName, }, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().GetByName(mock.Anything, "test-id").Return(user.User{ ID: "test-id", Name: "test", @@ -665,7 +668,7 @@ func TestService_Sudo(t *testing.T) { ID: schema.PlatformID, Namespace: schema.PlatformNamespace, }, - RelationName: schema.PlatformSudoPermission, + RelationName: schema.AdminRelationName, }, }).Return([]relation.CheckPair{ { @@ -678,7 +681,7 @@ func TestService_Sudo(t *testing.T) { ID: schema.PlatformID, Namespace: schema.PlatformNamespace, }, - RelationName: schema.PlatformSudoPermission, + RelationName: schema.AdminRelationName, }, Status: false, }, @@ -695,7 +698,11 @@ func TestService_Sudo(t *testing.T) { }, RelationName: schema.AdminRelationName, }).Return(relation.Relation{}, nil) - return user.NewService(repo, relationService, sessionService) + + auditRecordRepository.EXPECT().Create(mock.Anything, mock.MatchedBy(func(r models.AuditRecord) bool { + return r.Event == pkgAuditRecord.PlatformAdminAddedEvent && r.Target != nil && r.Target.ID == "test-id" + })).Return(models.AuditRecord{}, nil) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -706,7 +713,7 @@ func TestService_Sudo(t *testing.T) { relationName: schema.AdminRelationName, }, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().GetByName(mock.Anything, "test-id").Return(user.User{ ID: "test-id", Name: "test", @@ -722,7 +729,7 @@ func TestService_Sudo(t *testing.T) { ID: schema.PlatformID, Namespace: schema.PlatformNamespace, }, - RelationName: schema.PlatformSudoPermission, + RelationName: schema.AdminRelationName, }, }).Return([]relation.CheckPair{ { @@ -735,12 +742,12 @@ func TestService_Sudo(t *testing.T) { ID: schema.PlatformID, Namespace: schema.PlatformNamespace, }, - RelationName: schema.PlatformSudoPermission, + RelationName: schema.AdminRelationName, }, Status: true, }, }, nil) - return user.NewService(repo, relationService, sessionService) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { @@ -751,7 +758,7 @@ func TestService_Sudo(t *testing.T) { relationName: schema.MemberRelationName, }, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().GetByEmail(mock.Anything, "test@test.com").Return(user.User{}, user.ErrNotExist) repo.EXPECT().Create(mock.Anything, user.User{ @@ -775,7 +782,7 @@ func TestService_Sudo(t *testing.T) { ID: schema.PlatformID, Namespace: schema.PlatformNamespace, }, - RelationName: schema.PlatformCheckPermission, + RelationName: schema.MemberRelationName, }, }).Return([]relation.CheckPair{ { @@ -788,7 +795,7 @@ func TestService_Sudo(t *testing.T) { ID: schema.PlatformID, Namespace: schema.PlatformNamespace, }, - RelationName: schema.PlatformCheckPermission, + RelationName: schema.MemberRelationName, }, Status: false, }, @@ -805,7 +812,11 @@ func TestService_Sudo(t *testing.T) { }, RelationName: schema.MemberRelationName, }).Return(relation.Relation{}, nil) - return user.NewService(repo, relationService, sessionService) + + auditRecordRepository.EXPECT().Create(mock.Anything, mock.MatchedBy(func(r models.AuditRecord) bool { + return r.Event == pkgAuditRecord.PlatformMemberAddedEvent && r.Target != nil && r.Target.ID == "test-id" + })).Return(models.AuditRecord{}, nil) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, } @@ -821,8 +832,38 @@ func TestService_Sudo(t *testing.T) { func TestService_UnSudo(t *testing.T) { type args struct { - id string + id string + relationName string + } + + adminCheckRelations := []relation.Relation{ + { + Subject: relation.Subject{ + ID: "test-id", + Namespace: schema.UserPrincipal, + }, + Object: relation.Object{ + ID: schema.PlatformID, + Namespace: schema.PlatformNamespace, + }, + RelationName: schema.AdminRelationName, + }, + } + + memberCheckRelations := []relation.Relation{ + { + Subject: relation.Subject{ + ID: "test-id", + Namespace: schema.UserPrincipal, + }, + Object: relation.Object{ + ID: schema.PlatformID, + Namespace: schema.PlatformNamespace, + }, + RelationName: schema.MemberRelationName, + }, } + tests := []struct { name string setup func() *user.Service @@ -830,47 +871,77 @@ func TestService_UnSudo(t *testing.T) { wantErr bool }{ { - name: "remove user member permission of platform", + name: "removes admin relation and audits the revoke", wantErr: false, args: args{ - id: "test-id", + id: "test-id", + relationName: schema.AdminRelationName, }, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().GetByName(mock.Anything, "test-id").Return(user.User{ ID: "test-id", Name: "test", }, nil) - relationService.EXPECT().BatchCheckPermission(mock.Anything, []relation.Relation{ - { - Subject: relation.Subject{ - ID: "test-id", - Namespace: schema.UserPrincipal, - }, - Object: relation.Object{ - ID: schema.PlatformID, - Namespace: schema.PlatformNamespace, - }, - RelationName: schema.PlatformCheckPermission, + relationService.EXPECT().BatchCheckPermission(mock.Anything, adminCheckRelations). + Return([]relation.CheckPair{{Relation: adminCheckRelations[0], Status: true}}, nil) + + relationService.EXPECT().Delete(mock.Anything, relation.Relation{ + Object: relation.Object{ + ID: schema.PlatformID, + Namespace: schema.PlatformNamespace, }, - }).Return([]relation.CheckPair{ - { - Relation: relation.Relation{ - Subject: relation.Subject{ - ID: "test-id", - Namespace: schema.UserPrincipal, - }, - Object: relation.Object{ - ID: schema.PlatformID, - Namespace: schema.PlatformNamespace, - }, - RelationName: schema.PlatformCheckPermission, - }, - Status: true, + Subject: relation.Subject{ + ID: "test-id", + Namespace: schema.UserPrincipal, }, + RelationName: schema.AdminRelationName, + }).Return(nil) + + auditRecordRepository.EXPECT().Create(mock.Anything, mock.MatchedBy(func(r models.AuditRecord) bool { + return r.Event == pkgAuditRecord.PlatformAdminRemovedEvent && r.Target != nil && r.Target.ID == "test-id" + })).Return(models.AuditRecord{}, nil) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) + }, + }, + { + name: "admin removal is a no-op when user is not a superuser", + wantErr: false, + args: args{ + id: "test-id", + relationName: schema.AdminRelationName, + }, + setup: func() *user.Service { + repo, relationService, sessionService, auditRecordRepository := mockService(t) + repo.EXPECT().GetByName(mock.Anything, "test-id").Return(user.User{ + ID: "test-id", + Name: "test", }, nil) + relationService.EXPECT().BatchCheckPermission(mock.Anything, adminCheckRelations). + Return([]relation.CheckPair{{Relation: adminCheckRelations[0], Status: false}}, nil) + // no Delete, no audit + return user.NewService(repo, relationService, sessionService, auditRecordRepository) + }, + }, + { + name: "removes member relation and audits the revoke", + wantErr: false, + args: args{ + id: "test-id", + relationName: schema.MemberRelationName, + }, + setup: func() *user.Service { + repo, relationService, sessionService, auditRecordRepository := mockService(t) + repo.EXPECT().GetByName(mock.Anything, "test-id").Return(user.User{ + ID: "test-id", + Name: "test", + }, nil) + + relationService.EXPECT().BatchCheckPermission(mock.Anything, memberCheckRelations). + Return([]relation.CheckPair{{Relation: memberCheckRelations[0], Status: true}}, nil) + relationService.EXPECT().Delete(mock.Anything, relation.Relation{ Object: relation.Object{ ID: schema.PlatformID, @@ -882,58 +953,50 @@ func TestService_UnSudo(t *testing.T) { }, RelationName: schema.MemberRelationName, }).Return(nil) - return user.NewService(repo, relationService, sessionService) + + auditRecordRepository.EXPECT().Create(mock.Anything, mock.MatchedBy(func(r models.AuditRecord) bool { + return r.Event == pkgAuditRecord.PlatformMemberRemovedEvent && r.Target != nil && r.Target.ID == "test-id" + })).Return(models.AuditRecord{}, nil) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, { - name: "don't remove user member if already removed", + name: "member removal is a no-op when the relation is absent", wantErr: false, args: args{ - id: "test-id", + id: "test-id", + relationName: schema.MemberRelationName, }, setup: func() *user.Service { - repo, relationService, sessionService := mockService(t) + repo, relationService, sessionService, auditRecordRepository := mockService(t) repo.EXPECT().GetByName(mock.Anything, "test-id").Return(user.User{ ID: "test-id", Name: "test", }, nil) - relationService.EXPECT().BatchCheckPermission(mock.Anything, []relation.Relation{ - { - Subject: relation.Subject{ - ID: "test-id", - Namespace: schema.UserPrincipal, - }, - Object: relation.Object{ - ID: schema.PlatformID, - Namespace: schema.PlatformNamespace, - }, - RelationName: schema.PlatformCheckPermission, - }, - }).Return([]relation.CheckPair{ - { - Relation: relation.Relation{ - Subject: relation.Subject{ - ID: "test-id", - Namespace: schema.UserPrincipal, - }, - Object: relation.Object{ - ID: schema.PlatformID, - Namespace: schema.PlatformNamespace, - }, - RelationName: schema.PlatformCheckPermission, - }, - Status: false, - }, - }, nil) - return user.NewService(repo, relationService, sessionService) + relationService.EXPECT().BatchCheckPermission(mock.Anything, memberCheckRelations). + Return([]relation.CheckPair{{Relation: memberCheckRelations[0], Status: false}}, nil) + // no Delete, no audit + return user.NewService(repo, relationService, sessionService, auditRecordRepository) + }, + }, + { + name: "rejects an invalid relation name", + wantErr: true, + args: args{ + id: "test-id", + relationName: "owner", + }, + setup: func() *user.Service { + repo, relationService, sessionService, auditRecordRepository := mockService(t) + return user.NewService(repo, relationService, sessionService, auditRecordRepository) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := tt.setup() - if err := s.UnSudo(context.Background(), tt.args.id); (err != nil) != tt.wantErr { + if err := s.UnSudo(context.Background(), tt.args.id, tt.args.relationName); (err != nil) != tt.wantErr { t.Errorf("UnSudo() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/internal/api/v1beta1connect/interfaces.go b/internal/api/v1beta1connect/interfaces.go index e590241a6..0853fe1ad 100644 --- a/internal/api/v1beta1connect/interfaces.go +++ b/internal/api/v1beta1connect/interfaces.go @@ -142,7 +142,7 @@ type UserService interface { Disable(ctx context.Context, id string) error IsSudo(ctx context.Context, id string, permissionName string) (bool, error) Sudo(ctx context.Context, id string, relationName string) error - UnSudo(ctx context.Context, id string) error + UnSudo(ctx context.Context, id string, relationName string) error Search(ctx context.Context, rql *rql.Query) (user.SearchUserResponse, error) Export(ctx context.Context) ([]byte, string, error) } @@ -280,7 +280,7 @@ type ServiceUserService interface { ListByOrg(ctx context.Context, orgID string) ([]serviceuser.ServiceUser, error) IsSudo(ctx context.Context, id string, permissionName string) (bool, error) Sudo(ctx context.Context, id string, relationName string) error - UnSudo(ctx context.Context, id string) error + UnSudo(ctx context.Context, id string, relationName string) error GetByIDs(ctx context.Context, ids []string) ([]serviceuser.ServiceUser, error) } diff --git a/internal/api/v1beta1connect/mocks/service_user_service.go b/internal/api/v1beta1connect/mocks/service_user_service.go index 94bc82c5b..a742990dd 100644 --- a/internal/api/v1beta1connect/mocks/service_user_service.go +++ b/internal/api/v1beta1connect/mocks/service_user_service.go @@ -1070,17 +1070,17 @@ func (_c *ServiceUserService_Sudo_Call) RunAndReturn(run func(context.Context, s return _c } -// UnSudo provides a mock function with given fields: ctx, id -func (_m *ServiceUserService) UnSudo(ctx context.Context, id string) error { - ret := _m.Called(ctx, id) +// UnSudo provides a mock function with given fields: ctx, id, relationName +func (_m *ServiceUserService) UnSudo(ctx context.Context, id string, relationName string) error { + ret := _m.Called(ctx, id, relationName) if len(ret) == 0 { panic("no return value specified for UnSudo") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, id) + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, id, relationName) } else { r0 = ret.Error(0) } @@ -1096,13 +1096,14 @@ type ServiceUserService_UnSudo_Call struct { // UnSudo is a helper method to define mock.On call // - ctx context.Context // - id string -func (_e *ServiceUserService_Expecter) UnSudo(ctx interface{}, id interface{}) *ServiceUserService_UnSudo_Call { - return &ServiceUserService_UnSudo_Call{Call: _e.mock.On("UnSudo", ctx, id)} +// - relationName string +func (_e *ServiceUserService_Expecter) UnSudo(ctx interface{}, id interface{}, relationName interface{}) *ServiceUserService_UnSudo_Call { + return &ServiceUserService_UnSudo_Call{Call: _e.mock.On("UnSudo", ctx, id, relationName)} } -func (_c *ServiceUserService_UnSudo_Call) Run(run func(ctx context.Context, id string)) *ServiceUserService_UnSudo_Call { +func (_c *ServiceUserService_UnSudo_Call) Run(run func(ctx context.Context, id string, relationName string)) *ServiceUserService_UnSudo_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(string), args[2].(string)) }) return _c } @@ -1112,7 +1113,7 @@ func (_c *ServiceUserService_UnSudo_Call) Return(_a0 error) *ServiceUserService_ return _c } -func (_c *ServiceUserService_UnSudo_Call) RunAndReturn(run func(context.Context, string) error) *ServiceUserService_UnSudo_Call { +func (_c *ServiceUserService_UnSudo_Call) RunAndReturn(run func(context.Context, string, string) error) *ServiceUserService_UnSudo_Call { _c.Call.Return(run) return _c } diff --git a/internal/api/v1beta1connect/mocks/user_service.go b/internal/api/v1beta1connect/mocks/user_service.go index c06f82018..630943a07 100644 --- a/internal/api/v1beta1connect/mocks/user_service.go +++ b/internal/api/v1beta1connect/mocks/user_service.go @@ -5,10 +5,9 @@ package mocks import ( context "context" + user "github.com/raystack/frontier/core/user" rql "github.com/raystack/salt/rql" mock "github.com/stretchr/testify/mock" - - user "github.com/raystack/frontier/core/user" ) // UserService is an autogenerated mock type for the UserService type @@ -635,17 +634,17 @@ func (_c *UserService_Sudo_Call) RunAndReturn(run func(context.Context, string, return _c } -// UnSudo provides a mock function with given fields: ctx, id -func (_m *UserService) UnSudo(ctx context.Context, id string) error { - ret := _m.Called(ctx, id) +// UnSudo provides a mock function with given fields: ctx, id, relationName +func (_m *UserService) UnSudo(ctx context.Context, id string, relationName string) error { + ret := _m.Called(ctx, id, relationName) if len(ret) == 0 { panic("no return value specified for UnSudo") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, id) + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, id, relationName) } else { r0 = ret.Error(0) } @@ -661,13 +660,14 @@ type UserService_UnSudo_Call struct { // UnSudo is a helper method to define mock.On call // - ctx context.Context // - id string -func (_e *UserService_Expecter) UnSudo(ctx interface{}, id interface{}) *UserService_UnSudo_Call { - return &UserService_UnSudo_Call{Call: _e.mock.On("UnSudo", ctx, id)} +// - relationName string +func (_e *UserService_Expecter) UnSudo(ctx interface{}, id interface{}, relationName interface{}) *UserService_UnSudo_Call { + return &UserService_UnSudo_Call{Call: _e.mock.On("UnSudo", ctx, id, relationName)} } -func (_c *UserService_UnSudo_Call) Run(run func(ctx context.Context, id string)) *UserService_UnSudo_Call { +func (_c *UserService_UnSudo_Call) Run(run func(ctx context.Context, id string, relationName string)) *UserService_UnSudo_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(string), args[2].(string)) }) return _c } @@ -677,7 +677,7 @@ func (_c *UserService_UnSudo_Call) Return(_a0 error) *UserService_UnSudo_Call { return _c } -func (_c *UserService_UnSudo_Call) RunAndReturn(run func(context.Context, string) error) *UserService_UnSudo_Call { +func (_c *UserService_UnSudo_Call) RunAndReturn(run func(context.Context, string, string) error) *UserService_UnSudo_Call { _c.Call.Return(run) return _c } diff --git a/internal/api/v1beta1connect/platform.go b/internal/api/v1beta1connect/platform.go index 4289d5cce..4066a1547 100644 --- a/internal/api/v1beta1connect/platform.go +++ b/internal/api/v1beta1connect/platform.go @@ -3,11 +3,11 @@ package v1beta1connect import ( "context" "fmt" + "sort" "connectrpc.com/connect" "github.com/raystack/frontier/core/relation" "github.com/raystack/frontier/internal/bootstrap/schema" - "github.com/raystack/frontier/pkg/utils" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" ) @@ -33,13 +33,28 @@ func (h *ConnectHandler) AddPlatformUser(ctx context.Context, req *connect.Reque } func (h *ConnectHandler) RemovePlatformUser(ctx context.Context, req *connect.Request[frontierv1beta1.RemovePlatformUserRequest]) (*connect.Response[frontierv1beta1.RemovePlatformUserResponse], error) { + // By default remove the principal from the platform entirely (both admin and + // member). When a relation is set, scope removal to just that one — e.g. to + // demote an admin to member. Each UnSudo is a no-op for a relation not held. + platformRelations := []string{schema.AdminRelationName, schema.MemberRelationName} + if rel := req.Msg.GetRelation(); rel != "" { + if !schema.IsPlatformRelation(rel) { + return nil, connect.NewError(connect.CodeInvalidArgument, ErrBadRequest) + } + platformRelations = []string{rel} + } + if req.Msg.GetUserId() != "" { - if err := h.userService.UnSudo(ctx, req.Msg.GetUserId()); err != nil { - return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("RemovePlatformUser.UserUnSudo: user_id=%s: %w", req.Msg.GetUserId(), err)) + for _, relationName := range platformRelations { + if err := h.userService.UnSudo(ctx, req.Msg.GetUserId(), relationName); err != nil { + return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("RemovePlatformUser.UserUnSudo: user_id=%s relation=%s: %w", req.Msg.GetUserId(), relationName, err)) + } } } else if req.Msg.GetServiceuserId() != "" { - if err := h.serviceUserService.UnSudo(ctx, req.Msg.GetServiceuserId()); err != nil { - return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("RemovePlatformUser.ServiceUserUnSudo: service_user_id=%s: %w", req.Msg.GetServiceuserId(), err)) + for _, relationName := range platformRelations { + if err := h.serviceUserService.UnSudo(ctx, req.Msg.GetServiceuserId(), relationName); err != nil { + return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("RemovePlatformUser.ServiceUserUnSudo: service_user_id=%s relation=%s: %w", req.Msg.GetServiceuserId(), relationName, err)) + } } } else { return nil, connect.NewError(connect.CodeInvalidArgument, ErrBadRequest) @@ -58,15 +73,15 @@ func (h *ConnectHandler) ListPlatformUsers(ctx context.Context, req *connect.Req return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("ListPlatformUsers.ListRelations: %w", err)) } - subjectRelationMap := make(map[string]string) + // A principal can hold both admin and member; collect every relation per + // subject (deduped) so callers see the full set rather than just whichever + // tuple happened to be listed last. The reconciler relies on this to know + // exactly which relations to revoke. + userRelations := platformRelationsBySubject(relations, schema.UserPrincipal) + serviceUserRelations := platformRelationsBySubject(relations, schema.ServiceUserPrincipal) // fetch users - userIDs := utils.Map(utils.Filter(relations, func(r relation.Relation) bool { - return r.Subject.Namespace == schema.UserPrincipal - }), func(r relation.Relation) string { - subjectRelationMap[r.Subject.ID] = r.RelationName - return r.Subject.ID - }) + userIDs := sortedSubjectIDs(userRelations) userPBs := make([]*frontierv1beta1.User, 0, len(userIDs)) if len(userIDs) > 0 { users, err := h.userService.GetByIDs(ctx, userIDs) @@ -77,7 +92,7 @@ func (h *ConnectHandler) ListPlatformUsers(ctx context.Context, req *connect.Req if u.Metadata == nil { u.Metadata = make(map[string]any) } - u.Metadata["relation"] = subjectRelationMap[u.ID] + stampPlatformRelations(u.Metadata, userRelations[u.ID]) userPB, err := transformUserToPB(u) if err != nil { return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("ListPlatformUsers.TransformUser: entity_id=%s: %w", u.ID, err)) @@ -87,12 +102,7 @@ func (h *ConnectHandler) ListPlatformUsers(ctx context.Context, req *connect.Req } // fetch service users - serviceUserIDs := utils.Map(utils.Filter(relations, func(r relation.Relation) bool { - return r.Subject.Namespace == schema.ServiceUserPrincipal - }), func(r relation.Relation) string { - subjectRelationMap[r.Subject.ID] = r.RelationName - return r.Subject.ID - }) + serviceUserIDs := sortedSubjectIDs(serviceUserRelations) serviceUserPBs := make([]*frontierv1beta1.ServiceUser, 0, len(serviceUserIDs)) if len(serviceUserIDs) > 0 { serviceUsers, err := h.serviceUserService.GetByIDs(ctx, serviceUserIDs) @@ -103,7 +113,7 @@ func (h *ConnectHandler) ListPlatformUsers(ctx context.Context, req *connect.Req if u.Metadata == nil { u.Metadata = make(map[string]any) } - u.Metadata["relation"] = subjectRelationMap[u.ID] + stampPlatformRelations(u.Metadata, serviceUserRelations[u.ID]) serviceUserPB, err := transformServiceUserToPB(u) if err != nil { return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("ListPlatformUsers.TransformServiceUser: entity_id=%s: %w", u.ID, err)) @@ -117,3 +127,54 @@ func (h *ConnectHandler) ListPlatformUsers(ctx context.Context, req *connect.Req Serviceusers: serviceUserPBs, }), nil } + +// platformRelationsBySubject groups platform relation names (admin/member) by +// subject id for the given principal namespace, deduped and sorted for stable +// output. +func platformRelationsBySubject(relations []relation.Relation, namespace string) map[string][]string { + sets := map[string]map[string]struct{}{} + for _, r := range relations { + if r.Subject.Namespace != namespace { + continue + } + if sets[r.Subject.ID] == nil { + sets[r.Subject.ID] = map[string]struct{}{} + } + sets[r.Subject.ID][r.RelationName] = struct{}{} + } + out := make(map[string][]string, len(sets)) + for id, set := range sets { + rels := make([]string, 0, len(set)) + for rel := range set { + rels = append(rels, rel) + } + sort.Strings(rels) + out[id] = rels + } + return out +} + +// sortedSubjectIDs returns the subject ids in deterministic order. +func sortedSubjectIDs(m map[string][]string) []string { + ids := make([]string, 0, len(m)) + for id := range m { + ids = append(ids, id) + } + sort.Strings(ids) + return ids +} + +// stampPlatformRelations records a principal's platform relations in its +// metadata: "relations" carries the full set (consumed by the reconciler) while +// "relation" keeps the first one for backward compatibility. +func stampPlatformRelations(md map[string]any, rels []string) { + if len(rels) == 0 { + return + } + md["relation"] = rels[0] + anyRels := make([]any, len(rels)) + for i, r := range rels { + anyRels[i] = r + } + md["relations"] = anyRels +} diff --git a/internal/api/v1beta1connect/platform_test.go b/internal/api/v1beta1connect/platform_test.go new file mode 100644 index 000000000..a4ef6a446 --- /dev/null +++ b/internal/api/v1beta1connect/platform_test.go @@ -0,0 +1,106 @@ +package v1beta1connect + +import ( + "context" + "testing" + + "connectrpc.com/connect" + "github.com/raystack/frontier/core/relation" + "github.com/raystack/frontier/core/user" + "github.com/raystack/frontier/internal/api/v1beta1connect/mocks" + "github.com/raystack/frontier/internal/bootstrap/schema" + frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestHandler_RemovePlatformUser(t *testing.T) { + t.Run("removes both admin and member relations for a user", func(t *testing.T) { + userSvc := mocks.NewUserService(t) + // both platform relations are stripped; each UnSudo is a no-op for a relation + // the user doesn't hold. + userSvc.EXPECT().UnSudo(mock.Anything, "u1", schema.AdminRelationName).Return(nil) + userSvc.EXPECT().UnSudo(mock.Anything, "u1", schema.MemberRelationName).Return(nil) + + h := &ConnectHandler{userService: userSvc} + resp, err := h.RemovePlatformUser(context.Background(), connect.NewRequest(&frontierv1beta1.RemovePlatformUserRequest{ + UserId: "u1", + })) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + t.Run("removes both admin and member relations for a service user", func(t *testing.T) { + serviceUserSvc := mocks.NewServiceUserService(t) + serviceUserSvc.EXPECT().UnSudo(mock.Anything, "s1", schema.AdminRelationName).Return(nil) + serviceUserSvc.EXPECT().UnSudo(mock.Anything, "s1", schema.MemberRelationName).Return(nil) + + h := &ConnectHandler{serviceUserService: serviceUserSvc} + resp, err := h.RemovePlatformUser(context.Background(), connect.NewRequest(&frontierv1beta1.RemovePlatformUserRequest{ + ServiceuserId: "s1", + })) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + t.Run("removes only the specified relation when relation is set", func(t *testing.T) { + userSvc := mocks.NewUserService(t) + // only the admin relation is stripped; an UnSudo for member would be an + // unexpected call and fail the mock. + userSvc.EXPECT().UnSudo(mock.Anything, "u1", schema.AdminRelationName).Return(nil) + + h := &ConnectHandler{userService: userSvc} + resp, err := h.RemovePlatformUser(context.Background(), connect.NewRequest(&frontierv1beta1.RemovePlatformUserRequest{ + UserId: "u1", + Relation: schema.AdminRelationName, + })) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + t.Run("rejects an invalid relation", func(t *testing.T) { + h := &ConnectHandler{} + resp, err := h.RemovePlatformUser(context.Background(), connect.NewRequest(&frontierv1beta1.RemovePlatformUserRequest{ + UserId: "u1", + Relation: "owner", + })) + assert.Error(t, err) + assert.Nil(t, resp) + }) + + t.Run("rejects a request with neither id", func(t *testing.T) { + h := &ConnectHandler{} + resp, err := h.RemovePlatformUser(context.Background(), connect.NewRequest(&frontierv1beta1.RemovePlatformUserRequest{})) + assert.Error(t, err) + assert.Nil(t, resp) + }) +} + +func TestHandler_ListPlatformUsers(t *testing.T) { + t.Run("reports every relation a principal holds, deduped", func(t *testing.T) { + relationSvc := mocks.NewRelationService(t) + userSvc := mocks.NewUserService(t) + + // u1 holds both admin and member on the platform. + relationSvc.EXPECT().List(mock.Anything, mock.Anything).Return([]relation.Relation{ + {Subject: relation.Subject{ID: "u1", Namespace: schema.UserPrincipal}, RelationName: schema.AdminRelationName}, + {Subject: relation.Subject{ID: "u1", Namespace: schema.UserPrincipal}, RelationName: schema.MemberRelationName}, + }, nil) + // a single, deduped id is fetched even though two tuples were listed. + userSvc.EXPECT().GetByIDs(mock.Anything, []string{"u1"}).Return([]user.User{{ID: "u1"}}, nil) + + h := &ConnectHandler{relationService: relationSvc, userService: userSvc} + resp, err := h.ListPlatformUsers(context.Background(), connect.NewRequest(&frontierv1beta1.ListPlatformUsersRequest{})) + assert.NoError(t, err) + if assert.Len(t, resp.Msg.GetUsers(), 1) { + fields := resp.Msg.GetUsers()[0].GetMetadata().GetFields() + got := []string{} + for _, v := range fields["relations"].GetListValue().GetValues() { + got = append(got, v.GetStringValue()) + } + assert.ElementsMatch(t, []string{schema.AdminRelationName, schema.MemberRelationName}, got) + // "relation" stays populated for backward compatibility. + assert.NotEmpty(t, fields["relation"].GetStringValue()) + } + }) +} diff --git a/pkg/auditrecord/consts.go b/pkg/auditrecord/consts.go index 7dfeb21e7..cb2078a16 100644 --- a/pkg/auditrecord/consts.go +++ b/pkg/auditrecord/consts.go @@ -67,6 +67,12 @@ const ( // Session Events SessionRevokedEvent Event = "session.revoked" + // Platform Events + PlatformAdminAddedEvent Event = "platform.admin_added" + PlatformAdminRemovedEvent Event = "platform.admin_removed" + PlatformMemberAddedEvent Event = "platform.member_added" + PlatformMemberRemovedEvent Event = "platform.member_removed" + // Resource Events ResourceCreatedEvent Event = "resource.created" @@ -96,6 +102,7 @@ const ( BillingTransactionType EntityType = "billing_transaction" SessionType EntityType = "session" PATType EntityType = "pat" + PlatformType EntityType = "platform" ) // String returns the string representation of the event diff --git a/proto/v1beta1/admin.pb.go b/proto/v1beta1/admin.pb.go index 85160b973..5430cfe29 100644 --- a/proto/v1beta1/admin.pb.go +++ b/proto/v1beta1/admin.pb.go @@ -1941,6 +1941,9 @@ type RemovePlatformUserRequest struct { UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` ServiceuserId string `protobuf:"bytes,2,opt,name=serviceuser_id,json=serviceuserId,proto3" json:"serviceuser_id,omitempty"` + // relation scopes the removal to a single platform relation (admin or member). + // When empty, all platform relations of the principal are removed. + Relation string `protobuf:"bytes,3,opt,name=relation,proto3" json:"relation,omitempty"` } func (x *RemovePlatformUserRequest) Reset() { @@ -1989,6 +1992,13 @@ func (x *RemovePlatformUserRequest) GetServiceuserId() string { return "" } +func (x *RemovePlatformUserRequest) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + type RemovePlatformUserResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -7946,222 +7956,484 @@ var file_raystack_frontier_v1beta1_admin_proto_rawDesc = []byte{ 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x0c, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x75, 0x73, 0x65, 0x72, 0x73, 0x22, 0x5b, 0x0a, 0x19, 0x52, 0x65, 0x6d, + 0x76, 0x69, 0x63, 0x65, 0x75, 0x73, 0x65, 0x72, 0x73, 0x22, 0x77, 0x0a, 0x19, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, - 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9d, 0x02, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, - 0x65, 0x64, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1e, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, - 0x64, 0x12, 0x2c, 0x0a, 0x0a, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xba, 0x48, 0x08, 0xd8, 0x01, 0x01, 0x72, 0x03, 0xb0, - 0x01, 0x01, 0x18, 0x01, 0x52, 0x09, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, - 0x60, 0x0a, 0x11, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x62, 0x6f, 0x64, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x72, 0x61, 0x79, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x53, - 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x64, 0x79, 0x52, - 0x10, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x64, - 0x79, 0x12, 0x51, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x62, 0x6f, 0x64, - 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, - 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x50, 0x72, 0x6f, 0x64, - 0x75, 0x63, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, - 0x42, 0x6f, 0x64, 0x79, 0x22, 0xa6, 0x01, 0x0a, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, - 0x65, 0x64, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x3c, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x22, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, - 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, - 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x7f, 0x0a, - 0x16, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x27, - 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xd8, 0x01, 0x01, 0x1a, 0x02, 0x28, 0x01, 0x52, 0x08, 0x70, - 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x25, 0x0a, 0x08, 0x70, 0x61, 0x67, 0x65, 0x5f, - 0x6e, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xd8, 0x01, - 0x01, 0x1a, 0x02, 0x28, 0x01, 0x52, 0x07, 0x70, 0x61, 0x67, 0x65, 0x4e, 0x75, 0x6d, 0x22, 0x6f, - 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x08, 0x69, 0x6e, 0x76, - 0x6f, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x61, - 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, - 0x08, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, - 0x19, 0x0a, 0x17, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x1a, 0x0a, 0x18, 0x47, 0x65, - 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, - 0x6c, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x22, 0x76, - 0x0a, 0x1e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x54, 0x0a, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x72, 0x61, 0x79, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x0f, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0xc9, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x76, 0x65, 0x72, - 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0a, 0x70, - 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x02, 0x18, 0x01, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x12, 0x2c, - 0x0a, 0x0a, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x0d, 0xba, 0x48, 0x08, 0xd8, 0x01, 0x01, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x18, - 0x01, 0x52, 0x09, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x08, - 0x75, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, - 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x07, 0x75, 0x73, 0x61, 0x67, 0x65, 0x49, - 0x64, 0x12, 0x1f, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x28, 0x00, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x42, 0x69, 0x6c, 0x6c, - 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0xf9, 0x02, 0x0a, 0x12, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x1a, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x52, 0x03, - 0x75, 0x72, 0x6c, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, - 0x62, 0x65, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x10, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x73, 0x12, 0x54, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6c, 0x61, 0x74, + 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x9d, 0x02, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, + 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, + 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x2c, 0x0a, + 0x0a, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x0d, 0xba, 0x48, 0x08, 0xd8, 0x01, 0x01, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x18, 0x01, + 0x52, 0x09, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x60, 0x0a, 0x11, 0x73, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6f, 0x64, 0x79, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x10, 0x73, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x51, 0x0a, + 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, - 0x64, 0x79, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x31, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1b, 0xba, 0x48, 0x18, 0xd8, 0x01, 0x01, 0x72, - 0x13, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, 0x08, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x1a, 0x3a, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x59, 0x0a, 0x14, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x41, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, - 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x57, - 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, - 0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x55, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x3c, 0x0a, 0x07, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x57, 0x65, - 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x07, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x22, 0x76, - 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x0b, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x41, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x2d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x57, 0x65, - 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, - 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x55, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3c, 0x0a, 0x07, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x22, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, - 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x57, 0x65, 0x62, - 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x07, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x22, 0x33, 0x0a, - 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x0b, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, - 0x69, 0x64, 0x22, 0x17, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, - 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x4c, - 0x69, 0x73, 0x74, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x56, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, - 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x08, 0x77, 0x65, - 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x42, + 0x6f, 0x64, 0x79, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x42, 0x6f, 0x64, 0x79, + 0x22, 0xa6, 0x01, 0x0a, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, + 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, + 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x73, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, 0x07, 0x70, + 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, + 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x52, 0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x7f, 0x0a, 0x16, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x09, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0a, 0xba, + 0x48, 0x07, 0xd8, 0x01, 0x01, 0x1a, 0x02, 0x28, 0x01, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, + 0x69, 0x7a, 0x65, 0x12, 0x25, 0x0a, 0x08, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xd8, 0x01, 0x01, 0x1a, 0x02, 0x28, + 0x01, 0x52, 0x07, 0x70, 0x61, 0x67, 0x65, 0x4e, 0x75, 0x6d, 0x22, 0x6f, 0x0a, 0x17, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x08, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, + 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x08, 0x69, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x19, 0x0a, 0x17, 0x47, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x1a, 0x0a, 0x18, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, + 0x74, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x36, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x42, 0x69, 0x6c, + 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x22, 0x76, 0x0a, 0x1e, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x10, + 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x52, 0x0f, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x73, 0x22, 0xc9, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x42, 0x69, 0x6c, + 0x6c, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, + 0x09, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x0a, 0x62, 0x69, + 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, + 0xba, 0x48, 0x08, 0xd8, 0x01, 0x01, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x18, 0x01, 0x52, 0x09, 0x62, + 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x08, 0x75, 0x73, 0x61, 0x67, + 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, + 0x03, 0xb0, 0x01, 0x01, 0x52, 0x07, 0x75, 0x73, 0x61, 0x67, 0x65, 0x49, 0x64, 0x12, 0x1f, 0x0a, + 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, + 0x48, 0x04, 0x22, 0x02, 0x28, 0x00, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x1c, + 0x0a, 0x1a, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x55, + 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xf9, 0x02, 0x0a, + 0x12, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, + 0x6f, 0x64, 0x79, 0x12, 0x1a, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, + 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x5f, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x73, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x54, + 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x3a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x57, 0x65, 0x62, 0x68, + 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x2e, 0x48, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x73, 0x12, 0x31, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x1b, 0xba, 0x48, 0x18, 0xd8, 0x01, 0x01, 0x72, 0x13, 0x52, 0x07, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, + 0x63, 0x74, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3a, 0x0a, 0x0c, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x59, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x41, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, + 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, + 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x04, 0x62, + 0x6f, 0x64, 0x79, 0x22, 0x55, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x65, 0x62, + 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x07, + 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, + 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, + 0x6b, 0x52, 0x07, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x22, 0x76, 0x0a, 0x14, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, + 0xe0, 0x41, 0x02, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x41, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, + 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, + 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x04, 0x62, 0x6f, + 0x64, 0x79, 0x22, 0x55, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, + 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x77, + 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, - 0x52, 0x08, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x22, 0x7f, 0x0a, 0x21, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1e, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, - 0x1b, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xba, 0x48, 0x08, - 0xd8, 0x01, 0x01, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x09, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4d, 0x69, 0x6e, 0x22, 0x24, 0x0a, 0x22, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x5f, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x05, - 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xd8, 0x01, 0x01, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, - 0x69, 0x64, 0x22, 0x61, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, - 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x64, - 0x69, 0x74, 0x4d, 0x69, 0x6e, 0x12, 0x1e, 0x0a, 0x0b, 0x64, 0x75, 0x65, 0x5f, 0x69, 0x6e, 0x5f, - 0x64, 0x61, 0x79, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x64, 0x75, 0x65, 0x49, - 0x6e, 0x44, 0x61, 0x79, 0x73, 0x22, 0xaa, 0x01, 0x0a, 0x22, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, - 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x06, - 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, - 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x1b, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xd8, 0x01, - 0x01, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, - 0x65, 0x64, 0x69, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, - 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4d, 0x69, 0x6e, 0x12, 0x27, 0x0a, 0x0b, 0x64, 0x75, 0x65, - 0x5f, 0x69, 0x6e, 0x5f, 0x64, 0x61, 0x79, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, - 0xba, 0x48, 0x04, 0x22, 0x02, 0x28, 0x00, 0x52, 0x09, 0x64, 0x75, 0x65, 0x49, 0x6e, 0x44, 0x61, - 0x79, 0x73, 0x22, 0x25, 0x0a, 0x23, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x69, 0x6c, 0x6c, + 0x52, 0x07, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x22, 0x33, 0x0a, 0x14, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1b, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xe0, + 0x41, 0x02, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x22, 0x17, + 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x57, + 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x56, + 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x08, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, + 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x08, 0x77, 0x65, + 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x22, 0x7f, 0x0a, 0x21, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4c, 0x69, + 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x06, 0x6f, + 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, + 0x72, 0x02, 0x10, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xd8, 0x01, 0x01, 0x72, + 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, + 0x69, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, + 0x65, 0x64, 0x69, 0x74, 0x4d, 0x69, 0x6e, 0x22, 0x24, 0x0a, 0x22, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4c, + 0x69, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5f, 0x0a, + 0x1f, 0x47, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1f, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, + 0x64, 0x12, 0x1b, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xba, + 0x48, 0x08, 0xd8, 0x01, 0x01, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x22, 0x61, + 0x0a, 0x20, 0x47, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x6d, 0x69, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4d, 0x69, + 0x6e, 0x12, 0x1e, 0x0a, 0x0b, 0x64, 0x75, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x64, 0x61, 0x79, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x64, 0x75, 0x65, 0x49, 0x6e, 0x44, 0x61, 0x79, + 0x73, 0x22, 0xaa, 0x01, 0x0a, 0x22, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5e, 0x0a, 0x19, 0x53, 0x65, 0x74, - 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x79, 0x63, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0x1c, 0x0a, 0x1a, 0x45, 0x78, 0x70, - 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x59, 0x0a, 0x1a, 0x53, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, + 0x01, 0x01, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xd8, 0x01, 0x01, 0x72, 0x03, 0xb0, + 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, + 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x64, + 0x69, 0x74, 0x4d, 0x69, 0x6e, 0x12, 0x27, 0x0a, 0x0b, 0x64, 0x75, 0x65, 0x5f, 0x69, 0x6e, 0x5f, + 0x64, 0x61, 0x79, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, + 0x02, 0x28, 0x00, 0x52, 0x09, 0x64, 0x75, 0x65, 0x49, 0x6e, 0x44, 0x61, 0x79, 0x73, 0x22, 0x25, + 0x0a, 0x23, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5e, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x4f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x79, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0x1c, 0x0a, 0x1a, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0x59, 0x0a, 0x1a, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, + 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x73, + 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x4b, 0x79, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x10, + 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6b, 0x79, 0x63, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, + 0x79, 0x63, 0x52, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x4b, 0x79, 0x63, 0x22, 0x1d, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4b, 0x79, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x77, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4b, 0x79, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x57, 0x0a, 0x11, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x5f, 0x6b, 0x79, 0x63, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, + 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x79, 0x63, 0x52, 0x10, 0x6f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4b, 0x79, 0x63, 0x22, 0xf9, 0x06, 0x0a, 0x1b, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x0d, 0x6f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, + 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, + 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x0d, 0x6f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x55, 0x0a, 0x0a, + 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, + 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, + 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0xc9, 0x04, 0x0a, 0x12, + 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x39, 0x0a, 0x0a, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, + 0x61, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, + 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, + 0x1b, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3b, 0x0a, 0x0c, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x18, 0xba, 0x48, 0x15, 0x72, 0x13, 0x52, 0x07, 0x70, 0x72, 0x65, 0x70, 0x61, + 0x69, 0x64, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x74, 0x70, 0x61, 0x69, 0x64, 0x52, 0x0b, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x55, 0x0a, 0x19, 0x73, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, + 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x16, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x45, 0x6e, 0x64, 0x41, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x2d, 0x0a, 0x12, 0x73, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x6c, 0x61, + 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x17, + 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x22, 0x53, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, + 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, + 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0xf9, 0x01, 0x0a, + 0x15, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x73, 0x70, 0x65, + 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x61, 0x79, 0x73, + 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x09, + 0x70, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, + 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x46, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x33, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x50, + 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xe0, 0x41, 0x02, 0xba, + 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x22, 0x56, 0x0a, + 0x13, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, + 0x73, 0x70, 0x65, 0x63, 0x74, 0x22, 0xae, 0x03, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1d, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xe0, 0x41, 0x02, + 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2a, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x16, 0xba, 0x48, + 0x13, 0xd8, 0x01, 0x01, 0x72, 0x0e, 0x32, 0x0c, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, + 0x20, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x05, 0x65, 0x6d, + 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0c, 0xe0, 0x41, 0x02, 0xba, 0x48, + 0x06, 0x72, 0x04, 0x10, 0x01, 0x60, 0x01, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x14, + 0x0a, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, + 0x68, 0x6f, 0x6e, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1c, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x16, 0x72, 0x14, + 0x10, 0x01, 0x32, 0x10, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, + 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x08, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x12, 0x51, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, + 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x73, 0x70, + 0x65, 0x63, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x0d, 0xe0, 0x41, 0x02, 0xba, + 0x48, 0x07, 0x82, 0x01, 0x04, 0x18, 0x01, 0x18, 0x02, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x32, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x1a, 0xba, 0x48, 0x17, 0xd8, 0x01, 0x01, 0x72, 0x12, 0x32, 0x10, 0x5e, 0x5b, 0x41, + 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x06, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, + 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, + 0x64, 0x12, 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x59, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3f, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, + 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, + 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, + 0x74, 0x22, 0x36, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x07, 0x72, 0x05, + 0x10, 0x01, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x22, 0x18, 0x0a, 0x16, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x8f, 0x03, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, + 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x16, 0xba, 0x48, 0x13, + 0xd8, 0x01, 0x01, 0x72, 0x0e, 0x32, 0x0c, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x20, + 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x05, 0x65, 0x6d, 0x61, + 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0c, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x06, + 0x72, 0x04, 0x10, 0x01, 0x60, 0x01, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x14, 0x0a, + 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x68, + 0x6f, 0x6e, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1c, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x16, 0x72, 0x14, 0x10, + 0x01, 0x32, 0x10, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, + 0x5d, 0x2b, 0x24, 0x52, 0x08, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x12, 0x51, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, + 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, + 0x63, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x0d, 0xe0, 0x41, 0x02, 0xba, 0x48, + 0x07, 0x82, 0x01, 0x04, 0x18, 0x01, 0x18, 0x02, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x32, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x1a, 0xba, 0x48, 0x17, 0xd8, 0x01, 0x01, 0x72, 0x12, 0x32, 0x10, 0x5e, 0x5b, 0x41, 0x2d, + 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x06, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, + 0x12, 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x59, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, + 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3f, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, + 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x72, + 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, + 0x22, 0x76, 0x0a, 0x1e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, + 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x05, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x81, 0x05, 0x0a, 0x1f, 0x53, 0x65, 0x61, + 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, + 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x09, + 0x6f, 0x72, 0x67, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x4b, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x08, 0x6f, 0x72, + 0x67, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, + 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, + 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, + 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0xd4, 0x02, 0x0a, 0x10, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x3e, 0x0a, 0x0d, 0x6f, 0x72, + 0x67, 0x5f, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x6f, + 0x72, 0x67, 0x4a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6c, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, + 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6c, 0x65, 0x5f, + 0x74, 0x69, 0x74, 0x6c, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, + 0x6c, 0x65, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6c, 0x65, + 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x6c, 0x65, + 0x49, 0x64, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, + 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x39, 0x0a, 0x1e, + 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, + 0x02, 0x10, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x79, 0x0a, 0x21, 0x53, 0x65, 0x61, 0x72, 0x63, + 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, + 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, - 0x72, 0x79, 0x22, 0x73, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x79, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x55, 0x0a, 0x10, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x6b, 0x79, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x61, 0x79, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x4b, 0x79, 0x63, 0x52, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x79, 0x63, 0x22, 0x1d, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x4f, - 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4b, 0x79, 0x63, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x77, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4b, 0x79, 0x63, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x11, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x6b, 0x79, 0x63, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x79, 0x63, 0x52, 0x10, 0x6f, - 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4b, 0x79, 0x63, 0x22, - 0xf9, 0x06, 0x0a, 0x1b, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x6f, 0x0a, 0x0d, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, - 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x52, 0x0d, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x72, 0x79, 0x22, 0xc3, 0x04, 0x0a, 0x22, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, + 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x0c, 0x6f, 0x72, 0x67, + 0x5f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x51, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, + 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x52, 0x0b, 0x6f, 0x72, 0x67, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, + 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, + 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, + 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0x87, + 0x02, 0x0a, 0x13, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, + 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x65, + 0x6d, 0x62, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, + 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x3c, 0x0a, 0x21, 0x45, 0x78, 0x70, 0x6f, + 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, + 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, + 0x10, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x71, 0x0a, 0x19, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, + 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x05, + 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x61, + 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x95, 0x04, 0x0a, 0x1a, 0x53, 0x65, + 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x6a, + 0x65, 0x63, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x41, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x73, + 0x65, 0x72, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, @@ -8172,158 +8444,81 @@ var file_raystack_frontier_v1beta1_admin_proto_rawDesc = []byte{ 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, - 0xc9, 0x04, 0x0a, 0x12, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, - 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, - 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, - 0x61, 0x74, 0x61, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x62, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x42, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x6e, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x3b, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xba, 0x48, 0x15, 0x72, 0x13, 0x52, 0x07, 0x70, - 0x72, 0x65, 0x70, 0x61, 0x69, 0x64, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x74, 0x70, 0x61, 0x69, 0x64, - 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x55, 0x0a, - 0x19, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x79, - 0x63, 0x6c, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x16, 0x73, 0x75, - 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x45, - 0x6e, 0x64, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, - 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x2d, - 0x0a, 0x12, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, - 0x74, 0x61, 0x74, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x73, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, - 0x0d, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0f, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, - 0x61, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x10, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x22, 0x53, 0x0a, 0x14, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, + 0xef, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, + 0x69, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, + 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6c, 0x65, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6c, + 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x6c, + 0x65, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x5f, + 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x49, + 0x64, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, + 0x64, 0x22, 0x3a, 0x0a, 0x1f, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x87, 0x01, + 0x0a, 0x2f, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, + 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, + 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, + 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0xce, 0x04, 0x0a, 0x30, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xbf, 0x01, 0x0a, + 0x24, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x6d, 0x2e, 0x72, 0x61, + 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, + 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x22, 0x6f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x75, + 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x55, + 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, - 0x51, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x22, 0xf9, 0x01, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, - 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x70, 0x72, - 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, + 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, + 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0xb8, 0x01, + 0x0a, 0x21, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x75, 0x73, 0x65, + 0x72, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x22, 0x51, 0x0a, 0x12, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, + 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, - 0x63, 0x74, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x73, 0x12, 0x55, 0x0a, - 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, - 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, - 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x33, 0x0a, 0x12, - 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, - 0xe0, 0x41, 0x02, 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, - 0x64, 0x22, 0x56, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x73, - 0x70, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x61, 0x79, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, - 0x08, 0x70, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x22, 0xae, 0x03, 0x0a, 0x15, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x0d, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0xb0, 0x01, 0x01, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x2a, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x16, 0xba, 0x48, 0x13, 0xd8, 0x01, 0x01, 0x72, 0x0e, 0x32, 0x0c, 0x5e, 0x5b, 0x41, 0x2d, - 0x5a, 0x61, 0x2d, 0x7a, 0x20, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x22, - 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0c, 0xe0, - 0x41, 0x02, 0xba, 0x48, 0x06, 0x72, 0x04, 0x10, 0x01, 0x60, 0x01, 0x52, 0x05, 0x65, 0x6d, 0x61, - 0x69, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1c, 0xe0, 0x41, 0x02, 0xba, - 0x48, 0x16, 0x72, 0x14, 0x10, 0x01, 0x32, 0x10, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, - 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x08, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, - 0x74, 0x79, 0x12, 0x51, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, - 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, - 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x0d, - 0xe0, 0x41, 0x02, 0xba, 0x48, 0x07, 0x82, 0x01, 0x04, 0x18, 0x01, 0x18, 0x02, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1a, 0xba, 0x48, 0x17, 0xd8, 0x01, 0x01, 0x72, 0x12, 0x32, - 0x10, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, - 0x24, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, - 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x76, 0x65, 0x72, - 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, - 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x59, 0x0a, 0x16, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, - 0x73, 0x70, 0x65, 0x63, 0x74, 0x22, 0x36, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, - 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xe0, 0x41, 0x02, 0xba, - 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x22, 0x18, 0x0a, - 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8f, 0x03, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x16, 0xba, 0x48, 0x13, 0xd8, 0x01, 0x01, 0x72, 0x0e, 0x32, 0x0c, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, - 0x61, 0x2d, 0x7a, 0x20, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, - 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0c, 0xe0, 0x41, - 0x02, 0xba, 0x48, 0x06, 0x72, 0x04, 0x10, 0x01, 0x60, 0x01, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, - 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1c, 0xe0, 0x41, 0x02, 0xba, 0x48, - 0x16, 0x72, 0x14, 0x10, 0x01, 0x32, 0x10, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, - 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x08, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x12, 0x51, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x2a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x72, - 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x0d, 0xe0, - 0x41, 0x02, 0xba, 0x48, 0x07, 0x82, 0x01, 0x04, 0x18, 0x01, 0x18, 0x02, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x1a, 0xba, 0x48, 0x17, 0xd8, 0x01, 0x01, 0x72, 0x12, 0x32, 0x10, - 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, - 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x69, - 0x66, 0x69, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x76, 0x65, 0x72, 0x69, - 0x66, 0x69, 0x65, 0x64, 0x12, 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x59, 0x0a, 0x16, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x73, - 0x70, 0x65, 0x63, 0x74, 0x22, 0x76, 0x0a, 0x1e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, - 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x81, 0x05, 0x0a, - 0x1f, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x68, 0x0a, 0x09, 0x6f, 0x72, 0x67, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, - 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, - 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, - 0x52, 0x08, 0x6f, 0x72, 0x67, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, + 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0xeb, 0x01, 0x0a, 0x13, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, + 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, + 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, @@ -8333,149 +8528,77 @@ var file_raystack_frontier_v1beta1_admin_proto_rawDesc = []byte{ 0x32, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0xd4, 0x02, 0x0a, 0x10, 0x4f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, - 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x3e, - 0x0a, 0x0d, 0x6f, 0x72, 0x67, 0x5f, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0b, 0x6f, 0x72, 0x67, 0x4a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x41, 0x74, 0x12, 0x14, - 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x1d, 0x0a, 0x0a, - 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x72, - 0x6f, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x0a, 0x72, 0x6f, 0x6c, 0x65, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, - 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, - 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x22, 0x39, 0x0a, 0x1e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, - 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x79, 0x0a, 0x21, 0x53, - 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, - 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, - 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, - 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0xc3, 0x04, 0x0a, 0x22, 0x53, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, - 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, - 0x0c, 0x6f, 0x72, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x51, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, - 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x2e, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, - 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x0b, 0x6f, 0x72, 0x67, 0x50, 0x72, 0x6f, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, - 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, - 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x1a, 0x87, 0x02, 0x0a, 0x13, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, - 0x69, 0x74, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, - 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x3c, 0x0a, 0x21, - 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, - 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x71, 0x0a, 0x19, 0x53, 0x65, - 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, - 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x95, 0x04, - 0x0a, 0x1a, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x0d, - 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, - 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x6a, 0x65, - 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, - 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, 0x67, - 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x61, 0x79, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, - 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x1a, 0xef, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, - 0x61, 0x69, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x72, - 0x6f, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, - 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0a, 0x72, 0x6f, 0x6c, 0x65, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, - 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, - 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x6a, - 0x65, 0x63, 0x74, 0x49, 0x64, 0x22, 0x3a, 0x0a, 0x1f, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4f, - 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x02, 0x69, - 0x64, 0x22, 0x87, 0x01, 0x0a, 0x2f, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, - 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, - 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3b, + 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x14, 0x0a, 0x12, 0x45, 0x78, 0x70, + 0x6f, 0x72, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x7a, 0x0a, 0x1e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x4f, 0x72, 0x67, + 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1b, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xe0, + 0x41, 0x02, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0xce, 0x04, 0x0a, 0x30, - 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0xbf, 0x01, 0x0a, 0x24, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x72, - 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x6d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, - 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, - 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x72, 0x67, - 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x55, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x22, - 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x75, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x98, 0x05, 0x0a, 0x1f, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x7a, 0x0a, 0x12, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x72, 0x61, + 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, + 0x65, 0x72, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x75, 0x73, 0x65, 0x72, 0x4f, 0x72, + 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, + 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, + 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, + 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0xd9, 0x02, 0x0a, 0x10, 0x55, + 0x73, 0x65, 0x72, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x72, 0x67, 0x5f, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x72, 0x67, 0x54, 0x69, + 0x74, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x6f, 0x72, 0x67, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x6f, 0x72, 0x67, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x23, 0x0a, + 0x0d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x73, + 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x6c, 0x65, 0x54, 0x69, 0x74, 0x6c, + 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x08, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x73, 0x12, 0x3e, 0x0a, + 0x0d, 0x6f, 0x72, 0x67, 0x5f, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x0b, 0x6f, 0x72, 0x67, 0x4a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x4f, 0x6e, 0x12, 0x17, 0x0a, + 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xa2, 0x01, 0x0a, 0x19, 0x53, 0x65, 0x61, 0x72, 0x63, + 0x68, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, + 0x01, 0x01, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x06, 0x6f, 0x72, + 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xe0, 0x41, 0x02, 0xba, + 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x3b, + 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, + 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x92, 0x05, 0x0a, 0x1a, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x0d, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x41, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, + 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, + 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, + 0x6a, 0x65, 0x63, 0x74, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, + 0x74, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, @@ -8485,918 +8608,807 @@ var file_raystack_frontier_v1beta1_admin_proto_rawDesc = []byte{ 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, - 0x70, 0x1a, 0xb8, 0x01, 0x0a, 0x21, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x2b, 0x0a, - 0x11, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x74, - 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x75, 0x73, 0x65, 0x72, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x22, 0x51, 0x0a, 0x12, - 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, - 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, - 0xeb, 0x01, 0x0a, 0x13, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, + 0x70, 0x1a, 0xec, 0x02, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, + 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, + 0x6a, 0x65, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x6a, + 0x65, 0x63, 0x74, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x10, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x4f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, + 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x73, + 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x54, 0x69, 0x74, 0x6c, + 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x07, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x21, 0x0a, + 0x0c, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x73, 0x18, 0x08, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x73, + 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x22, 0xc0, 0x03, 0x0a, 0x1e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x6d, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x51, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, + 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x72, 0x67, + 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x42, 0x6f, 0x64, 0x79, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x04, 0x62, 0x6f, + 0x64, 0x79, 0x1a, 0xae, 0x02, 0x0a, 0x17, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x2e, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1a, 0xe0, 0x41, + 0x02, 0xba, 0x48, 0x14, 0x72, 0x12, 0x32, 0x10, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, + 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x12, 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x64, 0x0a, 0x06, 0x61, 0x76, 0x61, + 0x74, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x4c, 0xba, 0x48, 0x49, 0xd8, 0x01, + 0x01, 0x72, 0x44, 0x18, 0x90, 0xa1, 0x0f, 0x32, 0x3e, 0x5e, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x69, + 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x28, 0x70, 0x6e, 0x67, 0x7c, 0x6a, 0x70, 0x67, 0x7c, 0x6a, 0x70, + 0x65, 0x67, 0x7c, 0x67, 0x69, 0x66, 0x29, 0x3b, 0x62, 0x61, 0x73, 0x65, 0x36, 0x34, 0x2c, 0x28, + 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2b, 0x2f, 0x5d, 0x2b, 0x3d, 0x7b, + 0x30, 0x2c, 0x32, 0x7d, 0x29, 0x2b, 0x24, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, + 0x32, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, + 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x04, + 0x72, 0x02, 0x60, 0x01, 0x52, 0x0d, 0x6f, 0x72, 0x67, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d, + 0x61, 0x69, 0x6c, 0x22, 0x6e, 0x0a, 0x1f, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0c, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x72, + 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x22, 0x54, 0x0a, 0x15, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x05, + 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x61, + 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0xa1, 0x04, 0x0a, 0x16, 0x53, 0x65, + 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x08, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0x55, - 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, - 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, - 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, - 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x14, 0x0a, - 0x12, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x22, 0x7a, 0x0a, 0x1e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, - 0x72, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x0b, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, - 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, - 0x98, 0x05, 0x0a, 0x1f, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x4f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x12, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6f, 0x72, 0x67, 0x61, - 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x4b, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, - 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, - 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, - 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x75, 0x73, - 0x65, 0x72, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, - 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, - 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0xd9, - 0x02, 0x0a, 0x10, 0x55, 0x73, 0x65, 0x72, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x72, - 0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, - 0x72, 0x67, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x67, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x67, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x72, 0x67, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x72, 0x67, 0x41, 0x76, 0x61, 0x74, 0x61, - 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, - 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6c, 0x65, - 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x74, 0x69, - 0x74, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x6c, 0x65, - 0x54, 0x69, 0x74, 0x6c, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, - 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, - 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x6f, 0x72, 0x67, 0x5f, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x5f, - 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x6f, 0x72, 0x67, 0x4a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x4f, - 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xa2, 0x01, 0x0a, 0x19, 0x53, - 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xe0, 0x41, 0x02, 0xba, 0x48, - 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x22, - 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, - 0xe0, 0x41, 0x02, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x05, 0x6f, 0x72, 0x67, - 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, + 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x52, 0x08, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, + 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, - 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, - 0x92, 0x05, 0x0a, 0x1a, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, - 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, - 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0x90, 0x02, 0x0a, 0x07, 0x49, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x6c, 0x69, 0x6e, 0x6b, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4c, + 0x69, 0x6e, 0x6b, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x15, + 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x67, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x67, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x72, 0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x72, 0x67, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x7d, 0x0a, + 0x25, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, + 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0xa1, 0x05, 0x0a, + 0x26, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x97, 0x01, 0x0a, 0x1a, 0x6f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x59, 0x2e, 0x72, + 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x18, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, + 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6a, - 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x55, 0x73, 0x65, - 0x72, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x50, 0x72, - 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, + 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, + 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, + 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x1a, 0x43, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x1a, 0xf8, 0x01, 0x0a, 0x17, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, + 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x65, + 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x49, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, + 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x22, 0x1c, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9d, + 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, + 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, + 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, + 0x73, 0x65, 0x72, 0x12, 0x49, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x75, + 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x72, 0x61, 0x79, 0x73, + 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x22, 0x3f, + 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x07, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xe0, 0x41, 0x02, 0xba, + 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, + 0x5a, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x08, 0x73, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, + 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x4c, 0x0a, 0x18, 0x52, + 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xe0, 0x41, 0x02, + 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x1b, 0x0a, 0x19, 0x52, 0x65, 0x76, + 0x6f, 0x6b, 0x65, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x56, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, + 0x64, 0x69, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x5e, + 0x0a, 0x19, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x41, 0x0a, 0x05, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, - 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, - 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x86, + 0x02, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0d, 0x61, + 0x75, 0x64, 0x69, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, + 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x41, + 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x0c, 0x61, 0x75, 0x64, 0x69, + 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0xec, 0x02, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, - 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x6a, 0x65, - 0x63, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, - 0x6a, 0x65, 0x63, 0x74, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x48, 0x0a, 0x12, - 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x4f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, - 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, - 0x74, 0x6c, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, - 0x54, 0x69, 0x74, 0x6c, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, - 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x41, 0x76, 0x61, - 0x74, 0x61, 0x72, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x22, 0xc0, 0x03, 0x0a, 0x1e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x6d, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x51, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x67, - 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x2e, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, - 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x1a, 0xae, 0x02, 0x0a, 0x17, 0x4f, 0x72, 0x67, 0x61, 0x6e, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, - 0x64, 0x79, 0x12, 0x2e, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x1a, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x14, 0x72, 0x12, 0x32, 0x10, 0x5e, 0x5b, 0x41, 0x2d, - 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, - 0x75, 0x63, 0x74, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x64, 0x0a, - 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x4c, 0xba, - 0x48, 0x49, 0xd8, 0x01, 0x01, 0x72, 0x44, 0x18, 0x90, 0xa1, 0x0f, 0x32, 0x3e, 0x5e, 0x64, 0x61, - 0x74, 0x61, 0x3a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x28, 0x70, 0x6e, 0x67, 0x7c, 0x6a, 0x70, - 0x67, 0x7c, 0x6a, 0x70, 0x65, 0x67, 0x7c, 0x67, 0x69, 0x66, 0x29, 0x3b, 0x62, 0x61, 0x73, 0x65, - 0x36, 0x34, 0x2c, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2b, 0x2f, - 0x5d, 0x2b, 0x3d, 0x7b, 0x30, 0x2c, 0x32, 0x7d, 0x29, 0x2b, 0x24, 0x52, 0x06, 0x61, 0x76, 0x61, - 0x74, 0x61, 0x72, 0x12, 0x32, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, - 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xe0, 0x41, - 0x02, 0xba, 0x48, 0x04, 0x72, 0x02, 0x60, 0x01, 0x52, 0x0d, 0x6f, 0x72, 0x67, 0x4f, 0x77, 0x6e, - 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x6e, 0x0a, 0x1f, 0x41, 0x64, 0x6d, 0x69, 0x6e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0c, 0x6f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x27, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, - 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x72, 0x67, - 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x6f, 0x72, 0x67, 0x61, 0x6e, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x54, 0x0a, 0x15, 0x53, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, - 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0xa1, 0x04, - 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x08, 0x69, 0x6e, 0x76, 0x6f, - 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x72, 0x61, 0x79, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, - 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x08, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, - 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, - 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0x90, - 0x02, 0x0a, 0x07, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, - 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x76, 0x6f, - 0x69, 0x63, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x67, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x67, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x72, 0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, - 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x72, 0x67, 0x54, 0x69, 0x74, 0x6c, - 0x65, 0x22, 0x7d, 0x0a, 0x25, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x03, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, - 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, - 0x51, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x22, 0xa1, 0x05, 0x0a, 0x26, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x97, 0x01, 0x0a, 0x1a, - 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x59, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, - 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, - 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x46, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, + 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x80, 0x01, 0x0a, 0x1d, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x41, + 0x54, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x06, 0x6f, 0x72, 0x67, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xe0, 0x41, 0x02, 0xba, 0x48, + 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x3b, 0x0a, + 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, + 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x81, 0x06, 0x0a, 0x1e, 0x53, + 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x50, 0x41, 0x54, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, + 0x11, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, + 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x41, 0x54, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x18, 0x6f, 0x72, 0x67, - 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, + 0x50, 0x41, 0x54, 0x52, 0x10, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x50, 0x61, 0x74, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x61, - 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, - 0x72, 0x6f, 0x75, 0x70, 0x1a, 0x43, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x1a, 0xf8, 0x01, 0x0a, 0x17, 0x4f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6f, - 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, - 0x49, 0x64, 0x12, 0x65, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, - 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, - 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x22, 0x1c, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x9d, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1f, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, - 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x73, 0x65, - 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x49, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, - 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, - 0x65, 0x72, 0x22, 0x3f, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, - 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, - 0xe0, 0x41, 0x02, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x22, 0x5a, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3e, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, - 0x4c, 0x0a, 0x18, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x0a, 0x73, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x0b, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x09, 0x73, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x1b, 0x0a, - 0x19, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x56, 0x0a, 0x17, 0x4c, 0x69, - 0x73, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, - 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x52, 0x51, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, - 0x72, 0x79, 0x22, 0x5e, 0x0a, 0x19, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x75, 0x64, 0x69, - 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x41, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, - 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x45, 0x78, - 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, - 0x72, 0x79, 0x22, 0x86, 0x02, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, - 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x4b, 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x0c, - 0x61, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x55, 0x0a, 0x0a, - 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, - 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, - 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, - 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x80, 0x01, 0x0a, 0x1d, + 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x47, 0x0a, 0x09, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, + 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x1a, 0xc6, 0x03, 0x0a, 0x0f, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x41, 0x54, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, + 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, + 0x62, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, + 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x50, 0x41, 0x54, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, - 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xe0, - 0x41, 0x02, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, - 0x64, 0x12, 0x3b, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x25, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, - 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x81, - 0x06, 0x0a, 0x1e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x41, 0x54, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x76, 0x0a, 0x11, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x70, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x72, - 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, - 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x41, 0x54, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x41, 0x54, 0x52, 0x10, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x61, 0x67, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, - 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x51, 0x4c, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x1a, 0x47, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, - 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, - 0x74, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x1a, 0xc6, 0x03, 0x0a, 0x0f, 0x4f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x41, 0x54, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, - 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, - 0x74, 0x6c, 0x65, 0x12, 0x62, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, - 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x41, 0x54, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x52, 0x09, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x3b, 0x0a, 0x06, 0x73, 0x63, 0x6f, 0x70, 0x65, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, - 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x50, 0x41, 0x54, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x06, 0x73, 0x63, - 0x6f, 0x70, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, - 0x39, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, + 0x6f, 0x6e, 0x50, 0x41, 0x54, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x42, 0x79, 0x12, 0x3b, 0x0a, 0x06, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, + 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, + 0x50, 0x41, 0x54, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x06, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, + 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x12, 0x33, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x06, 0x75, 0x73, 0x65, 0x64, 0x41, 0x74, 0x12, 0x41, 0x0a, 0x0e, 0x72, + 0x65, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x12, 0x33, 0x0a, 0x07, 0x75, 0x73, - 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x75, 0x73, 0x65, 0x64, 0x41, 0x74, 0x12, - 0x41, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, - 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x72, 0x65, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x22, 0x8f, 0x01, 0x0a, 0x1d, 0x41, 0x64, 0x64, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x05, - 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x4d, 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x4f, 0x72, 0x67, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x42, 0x08, 0xba, 0x48, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x07, 0x6d, 0x65, 0x6d, - 0x62, 0x65, 0x72, 0x73, 0x22, 0x56, 0x0a, 0x0e, 0x4f, 0x72, 0x67, 0x4d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, - 0x01, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x07, 0x72, 0x6f, 0x6c, - 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, - 0x03, 0xb0, 0x01, 0x01, 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x22, 0x66, 0x0a, 0x1e, - 0x41, 0x64, 0x64, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, - 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x2a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, - 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x72, 0x67, 0x4d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x73, 0x22, 0x73, 0x0a, 0x0f, 0x4f, 0x72, 0x67, 0x4d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x32, 0xfb, 0x3d, 0x0a, 0x0c, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x71, 0x0a, 0x0c, 0x4c, 0x69, - 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x2e, 0x72, 0x61, 0x79, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x55, 0x73, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x72, 0x61, 0x79, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x55, 0x73, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x86, 0x01, - 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x72, - 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x73, 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, - 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, - 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x89, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x4f, - 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x36, 0x2e, 0x72, - 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, - 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, - 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x92, 0x01, 0x0a, 0x17, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, - 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x2e, 0x72, 0x61, - 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x8f, 0x01, 0x0a, 0x16, 0x41, 0x64, 0x64, 0x4f, 0x72, 0x67, 0x61, - 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, - 0x38, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, - 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x4f, + 0x0d, 0x72, 0x65, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x8f, + 0x01, 0x0a, 0x1d, 0x41, 0x64, 0x64, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1f, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, + 0x64, 0x12, 0x4d, 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, + 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, + 0x72, 0x67, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x08, 0xba, + 0x48, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, + 0x22, 0x56, 0x0a, 0x0e, 0x4f, 0x72, 0x67, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x21, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x06, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, + 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x22, 0x66, 0x0a, 0x1e, 0x41, 0x64, 0x64, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x72, 0x61, 0x79, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x86, 0x01, 0x0a, 0x13, 0x53, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x35, - 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x92, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x39, 0x2e, 0x72, 0x61, + 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x07, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x72, 0x67, 0x4d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, + 0x22, 0x73, 0x0a, 0x0f, 0x4f, 0x72, 0x67, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, + 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, + 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x32, 0xfb, 0x3d, 0x0a, 0x0c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x71, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, + 0x6c, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x83, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, - 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x34, 0x2e, 0x72, 0x61, + 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x86, 0x01, 0x0a, 0x13, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, + 0x73, 0x12, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, + 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, + 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, + 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x89, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x36, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x37, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x92, 0x01, 0x0a, 0x17, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, + 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, + 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x8f, 0x01, 0x0a, 0x16, 0x41, 0x64, 0x64, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x38, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, - 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x4f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x41, 0x64, 0x64, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x86, 0x01, 0x0a, 0x13, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, + 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x35, 0x2e, 0x72, 0x61, 0x79, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, + 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x36, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, - 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x9b, 0x01, 0x0a, 0x1a, 0x53, - 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x3c, 0x2e, 0x72, 0x61, 0x79, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, - 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, + 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x92, 0x01, 0x0a, 0x17, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x39, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0xc5, 0x01, 0x0a, 0x28, 0x53, 0x65, 0x61, - 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x4a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, + 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, + 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x83, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, + 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, + 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, + 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x9b, 0x01, 0x0a, 0x1a, 0x53, 0x65, 0x61, 0x72, 0x63, + 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x3c, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, - 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x4b, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, + 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x3d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, + 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0xc5, 0x01, 0x0a, 0x28, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x73, 0x12, 0x4a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0xa7, 0x01, 0x0a, 0x1e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, - 0x65, 0x72, 0x73, 0x12, 0x40, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, - 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x41, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x66, 0x0a, 0x13, 0x45, 0x78, - 0x70, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x45, 0x78, - 0x70, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x00, - 0x30, 0x01, 0x12, 0x6e, 0x0a, 0x17, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, - 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x39, 0x2e, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x4b, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, - 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x00, - 0x30, 0x01, 0x12, 0x74, 0x0a, 0x1a, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, - 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, - 0x12, 0x3c, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, - 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x45, 0x78, 0x70, - 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, - 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, - 0x42, 0x6f, 0x64, 0x79, 0x22, 0x00, 0x30, 0x01, 0x12, 0x70, 0x0a, 0x18, 0x45, 0x78, 0x70, 0x6f, - 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x3a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, - 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x48, 0x74, - 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x00, 0x30, 0x01, 0x12, 0x56, 0x0a, 0x0b, 0x45, 0x78, - 0x70, 0x6f, 0x72, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x2e, 0x72, 0x61, 0x79, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x00, - 0x30, 0x01, 0x12, 0x6e, 0x0a, 0x0b, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, - 0x73, 0x12, 0x2d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, + 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, + 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0xa7, 0x01, 0x0a, + 0x1e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, + 0x40, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x41, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, - 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2e, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, - 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, - 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x92, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, - 0x72, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x39, - 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x55, 0x73, 0x65, 0x72, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x72, 0x61, 0x79, 0x73, + 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x66, 0x0a, 0x13, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, + 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x35, 0x2e, + 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, + 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x00, 0x30, 0x01, 0x12, 0x6e, + 0x0a, 0x17, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x39, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, - 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x83, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x61, 0x72, - 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x34, - 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, - 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x83, 0x01, - 0x0a, 0x12, 0x53, 0x65, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x4b, 0x79, 0x63, 0x12, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, - 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x53, 0x65, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x4b, 0x79, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x72, 0x61, 0x79, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x79, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x89, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x67, 0x61, - 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4b, 0x79, 0x63, 0x12, 0x36, 0x2e, 0x72, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x00, 0x30, 0x01, 0x12, 0x74, + 0x0a, 0x1a, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x3c, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x67, - 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4b, 0x79, 0x63, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, - 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x4b, 0x79, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x71, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, - 0x2e, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, - 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2f, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, - 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x74, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x2f, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, - 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, - 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2f, 0x2e, 0x72, 0x61, 0x79, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x72, 0x61, 0x79, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6b, - 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2c, 0x2e, 0x72, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, + 0x22, 0x00, 0x30, 0x01, 0x12, 0x70, 0x0a, 0x18, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4f, 0x72, + 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x12, 0x3a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x45, 0x78, 0x70, + 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, + 0x64, 0x79, 0x22, 0x00, 0x30, 0x01, 0x12, 0x56, 0x0a, 0x0b, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, + 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x00, 0x30, 0x01, 0x12, 0x6e, + 0x0a, 0x0b, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x2e, + 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, + 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, - 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x72, 0x61, 0x79, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, + 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x92, + 0x01, 0x0a, 0x17, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x4f, 0x72, 0x67, + 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x39, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x0a, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x79, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, - 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, + 0x72, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x4f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x83, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, + 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x34, 0x2e, 0x72, 0x61, 0x79, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, + 0x72, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, + 0x72, 0x63, 0x68, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x83, 0x01, 0x0a, 0x12, 0x53, 0x65, + 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x79, 0x63, + 0x12, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, + 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x79, 0x63, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, - 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, + 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x4b, 0x79, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x89, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4b, 0x79, 0x63, 0x12, 0x36, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4b, 0x79, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x37, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4b, 0x79, + 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x71, 0x0a, 0x0c, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x2e, 0x2e, 0x72, 0x61, + 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x72, 0x61, + 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, + 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x2f, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2f, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x0a, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, + 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, + 0x6c, 0x65, 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, + 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x7d, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, - 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x7d, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, + 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x72, - 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x7d, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, + 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x72, 0x61, - 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x7a, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, - 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x31, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, - 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x80, - 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, - 0x6e, 0x63, 0x65, 0x73, 0x12, 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7a, + 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, + 0x73, 0x12, 0x31, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, + 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x66, - 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0xad, 0x01, 0x0a, 0x20, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x65, 0x64, 0x65, 0x72, - 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x42, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x80, 0x01, 0x0a, 0x11, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, + 0x12, 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0xad, 0x01, + 0x0a, 0x20, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x42, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, + 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x43, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x43, 0x2e, 0x72, 0x61, 0x79, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x65, 0x64, 0x65, - 0x72, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x7a, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, - 0x55, 0x73, 0x65, 0x72, 0x12, 0x31, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7a, 0x0a, + 0x0f, 0x41, 0x64, 0x64, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, 0x65, 0x72, + 0x12, 0x31, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x41, 0x64, 0x64, + 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, + 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, + 0x41, 0x64, 0x64, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x80, 0x01, 0x0a, 0x11, 0x4c, 0x69, + 0x73, 0x74, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, + 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, 0x65, 0x72, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, - 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, - 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x80, 0x01, - 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, - 0x65, 0x72, 0x73, 0x12, 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, 0x65, + 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x83, 0x01, 0x0a, + 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, + 0x73, 0x65, 0x72, 0x12, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, + 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, + 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6c, 0x61, 0x74, + 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x80, 0x01, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x12, 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, - 0x6d, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x83, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6c, 0x61, 0x74, 0x66, - 0x6f, 0x72, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x12, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, - 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, - 0x72, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, - 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x80, 0x01, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x67, - 0x61, 0x74, 0x65, 0x64, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x12, 0x33, 0x2e, 0x72, - 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, - 0x65, 0x64, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7a, 0x0a, 0x0f, 0x4c, 0x69, 0x73, - 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x31, 0x2e, 0x72, - 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, - 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x41, 0x6c, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x10, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, - 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, + 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, + 0x74, 0x65, 0x64, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7a, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x31, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x72, 0x61, + 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x49, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x7d, 0x0a, 0x10, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, - 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, - 0x74, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x8f, 0x01, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, - 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, - 0x38, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, - 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x41, 0x6c, 0x6c, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x72, 0x61, 0x79, 0x73, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x8f, 0x01, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x42, 0x69, 0x6c, 0x6c, + 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x38, 0x2e, 0x72, 0x61, + 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x42, + 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x83, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x42, 0x69, 0x6c, + 0x6c, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x42, 0x69, 0x6c, - 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x83, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x76, 0x65, 0x72, - 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x34, 0x2e, - 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, - 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, - 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, - 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x0d, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x2f, 0x2e, - 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, - 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x74, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, - 0x6f, 0x6f, 0x6b, 0x12, 0x2f, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, - 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, - 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x42, 0x69, 0x6c, 0x6c, + 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x65, + 0x72, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x2f, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x65, 0x62, - 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x71, - 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x12, 0x2e, - 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, - 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, - 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, - 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x9e, 0x01, 0x0a, 0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x69, 0x6c, 0x6c, - 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, - 0x12, 0x3c, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x65, 0x62, + 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, + 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, + 0x2f, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3d, - 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4c, - 0x69, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, - 0x02, 0x01, 0x12, 0x95, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, - 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, - 0x3a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, - 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, - 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, - 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x72, 0x61, + 0x61, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x65, + 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x2f, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x71, 0x0a, 0x0c, 0x4c, 0x69, + 0x73, 0x74, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x12, 0x2e, 0x2e, 0x72, 0x61, 0x79, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x62, 0x68, 0x6f, + 0x6f, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x72, 0x61, 0x79, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x62, 0x68, 0x6f, + 0x6f, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x9e, 0x01, + 0x0a, 0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x3c, 0x2e, 0x72, + 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, + 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4c, 0x69, 0x6d, + 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3d, 0x2e, 0x72, 0x61, 0x79, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x69, 0x6c, + 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x95, + 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x3a, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x9e, 0x01, 0x0a, 0x1b, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x3d, 0x2e, 0x72, 0x61, 0x79, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x69, 0x6c, - 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3e, 0x2e, 0x72, 0x61, 0x79, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x69, 0x6c, 0x6c, - 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x77, 0x0a, 0x0e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x12, 0x30, 0x2e, - 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x31, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, - 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x73, - 0x70, 0x65, 0x63, 0x74, 0x73, 0x12, 0x2f, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, + 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x9e, 0x01, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x3d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6e, 0x0a, 0x0b, 0x47, 0x65, - 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x12, 0x2d, 0x2e, 0x72, 0x61, 0x79, 0x73, + 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3e, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x77, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x12, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, + 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x72, 0x61, + 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, + 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x74, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, + 0x73, 0x12, 0x2f, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, + 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, + 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6e, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, + 0x73, 0x70, 0x65, 0x63, 0x74, 0x12, 0x2d, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, + 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x77, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x12, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x77, 0x0a, 0x0e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x12, 0x30, 0x2e, 0x72, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x72, 0x61, 0x79, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, + 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x77, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, + 0x74, 0x12, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, + 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, + 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x77, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x30, 0x2e, 0x72, 0x61, 0x79, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, - 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, - 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x77, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, - 0x73, 0x70, 0x65, 0x63, 0x74, 0x12, 0x30, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, - 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, - 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x77, 0x0a, 0x0e, - 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x30, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x86, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x35, 0x2e, 0x72, 0x61, 0x79, 0x73, + 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x36, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x10, 0x4c, 0x69, + 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x31, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, - 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, - 0x72, 0x63, 0x68, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x86, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x35, 0x2e, - 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, - 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x6d, 0x69, 0x6e, - 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, - 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, + 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x80, 0x01, - 0x0a, 0x11, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, - 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x55, 0x73, 0x65, 0x72, 0x53, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x7d, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x73, 0x12, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x80, 0x01, 0x0a, 0x11, 0x52, 0x65, + 0x76, 0x6f, 0x6b, 0x65, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x6f, + 0x6b, 0x65, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, + 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x10, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, + 0x12, 0x32, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, - 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x64, 0x0a, 0x12, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, - 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, - 0x79, 0x22, 0x00, 0x30, 0x01, 0x12, 0x8f, 0x01, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, - 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x41, 0x54, 0x73, - 0x12, 0x38, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, - 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, - 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, - 0x41, 0x54, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x72, 0x61, 0x79, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, - 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x41, 0x54, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xed, 0x01, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, - 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x42, 0x0a, 0x41, 0x64, 0x6d, 0x69, 0x6e, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x66, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x3b, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x46, 0x58, 0xaa, 0x02, 0x19, 0x52, 0x61, 0x79, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x56, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0xca, 0x02, 0x19, 0x52, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x5c, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0xe2, 0x02, 0x25, 0x52, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5c, 0x46, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x5c, 0x47, 0x50, - 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1b, 0x52, 0x61, 0x79, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x3a, 0x3a, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x3a, 0x3a, - 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x64, 0x0a, 0x12, 0x45, + 0x78, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x12, 0x34, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, + 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x45, 0x78, + 0x70, 0x6f, 0x72, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x00, 0x30, + 0x01, 0x12, 0x8f, 0x01, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x41, 0x54, 0x73, 0x12, 0x38, 0x2e, 0x72, + 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x41, 0x54, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x41, 0x54, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x42, 0xed, 0x01, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x61, 0x79, 0x73, + 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x42, 0x0a, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x72, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, + 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, + 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xa2, + 0x02, 0x03, 0x52, 0x46, 0x58, 0xaa, 0x02, 0x19, 0x52, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x2e, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0xca, 0x02, 0x19, 0x52, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5c, 0x46, 0x72, 0x6f, + 0x6e, 0x74, 0x69, 0x65, 0x72, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, 0x02, 0x25, + 0x52, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5c, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, + 0x72, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1b, 0x52, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x3a, 0x3a, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( From c556cbd8cf91ae3906d6345fb155264f757830c8 Mon Sep 17 00:00:00 2001 From: Rohil Surana Date: Tue, 30 Jun 2026 17:27:33 +0530 Subject: [PATCH 02/10] fix(platform): normalize relation casing before Sudo/UnSudo on add and remove --- internal/api/v1beta1connect/platform.go | 6 ++- internal/api/v1beta1connect/platform_test.go | 40 ++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/internal/api/v1beta1connect/platform.go b/internal/api/v1beta1connect/platform.go index 4066a1547..d24145e70 100644 --- a/internal/api/v1beta1connect/platform.go +++ b/internal/api/v1beta1connect/platform.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "sort" + "strings" "connectrpc.com/connect" "github.com/raystack/frontier/core/relation" @@ -17,6 +18,8 @@ func (h *ConnectHandler) AddPlatformUser(ctx context.Context, req *connect.Reque if !schema.IsPlatformRelation(relationName) { return nil, connect.NewError(connect.CodeInvalidArgument, ErrBadRequest) } + // IsPlatformRelation is case-insensitive; normalize so it matches Sudo's exact switch. + relationName = strings.ToLower(relationName) if req.Msg.GetUserId() != "" { if err := h.userService.Sudo(ctx, req.Msg.GetUserId(), relationName); err != nil { @@ -41,7 +44,8 @@ func (h *ConnectHandler) RemovePlatformUser(ctx context.Context, req *connect.Re if !schema.IsPlatformRelation(rel) { return nil, connect.NewError(connect.CodeInvalidArgument, ErrBadRequest) } - platformRelations = []string{rel} + // normalize: IsPlatformRelation is case-insensitive but UnSudo matches exactly. + platformRelations = []string{strings.ToLower(rel)} } if req.Msg.GetUserId() != "" { diff --git a/internal/api/v1beta1connect/platform_test.go b/internal/api/v1beta1connect/platform_test.go index a4ef6a446..9f5815b94 100644 --- a/internal/api/v1beta1connect/platform_test.go +++ b/internal/api/v1beta1connect/platform_test.go @@ -58,6 +58,21 @@ func TestHandler_RemovePlatformUser(t *testing.T) { assert.NotNil(t, resp) }) + t.Run("normalizes a mixed-case relation before forwarding", func(t *testing.T) { + userSvc := mocks.NewUserService(t) + // "Admin" is valid (IsPlatformRelation is case-insensitive); UnSudo must + // receive the lowercase relation so its exact switch matches. + userSvc.EXPECT().UnSudo(mock.Anything, "u1", schema.AdminRelationName).Return(nil) + + h := &ConnectHandler{userService: userSvc} + resp, err := h.RemovePlatformUser(context.Background(), connect.NewRequest(&frontierv1beta1.RemovePlatformUserRequest{ + UserId: "u1", + Relation: "Admin", + })) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + t.Run("rejects an invalid relation", func(t *testing.T) { h := &ConnectHandler{} resp, err := h.RemovePlatformUser(context.Background(), connect.NewRequest(&frontierv1beta1.RemovePlatformUserRequest{ @@ -76,6 +91,31 @@ func TestHandler_RemovePlatformUser(t *testing.T) { }) } +func TestHandler_AddPlatformUser(t *testing.T) { + t.Run("normalizes a mixed-case relation before forwarding to Sudo", func(t *testing.T) { + userSvc := mocks.NewUserService(t) + userSvc.EXPECT().Sudo(mock.Anything, "u1", schema.AdminRelationName).Return(nil) + + h := &ConnectHandler{userService: userSvc} + resp, err := h.AddPlatformUser(context.Background(), connect.NewRequest(&frontierv1beta1.AddPlatformUserRequest{ + UserId: "u1", + Relation: "Admin", + })) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + t.Run("rejects an invalid relation", func(t *testing.T) { + h := &ConnectHandler{} + resp, err := h.AddPlatformUser(context.Background(), connect.NewRequest(&frontierv1beta1.AddPlatformUserRequest{ + UserId: "u1", + Relation: "owner", + })) + assert.Error(t, err) + assert.Nil(t, resp) + }) +} + func TestHandler_ListPlatformUsers(t *testing.T) { t.Run("reports every relation a principal holds, deduped", func(t *testing.T) { relationSvc := mocks.NewRelationService(t) From 66ce354d8a7fe5461e8fca2baf95b4ff9c7ed9de Mon Sep 17 00:00:00 2001 From: Rohil Surana Date: Wed, 1 Jul 2026 17:08:51 +0530 Subject: [PATCH 03/10] docs(platform): rewrite platform-user comments in plain english --- internal/api/v1beta1connect/platform.go | 29 +++++++++++++------------ 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/internal/api/v1beta1connect/platform.go b/internal/api/v1beta1connect/platform.go index d24145e70..3eec51c6b 100644 --- a/internal/api/v1beta1connect/platform.go +++ b/internal/api/v1beta1connect/platform.go @@ -36,9 +36,10 @@ func (h *ConnectHandler) AddPlatformUser(ctx context.Context, req *connect.Reque } func (h *ConnectHandler) RemovePlatformUser(ctx context.Context, req *connect.Request[frontierv1beta1.RemovePlatformUserRequest]) (*connect.Response[frontierv1beta1.RemovePlatformUserResponse], error) { - // By default remove the principal from the platform entirely (both admin and - // member). When a relation is set, scope removal to just that one — e.g. to - // demote an admin to member. Each UnSudo is a no-op for a relation not held. + // By default, remove the user or service account from the platform completely + // (both admin and member). If a relation is set, remove just that one — for + // example, to turn an admin into a member. UnSudo does nothing if they don't + // hold that relation. platformRelations := []string{schema.AdminRelationName, schema.MemberRelationName} if rel := req.Msg.GetRelation(); rel != "" { if !schema.IsPlatformRelation(rel) { @@ -77,10 +78,10 @@ func (h *ConnectHandler) ListPlatformUsers(ctx context.Context, req *connect.Req return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("ListPlatformUsers.ListRelations: %w", err)) } - // A principal can hold both admin and member; collect every relation per - // subject (deduped) so callers see the full set rather than just whichever - // tuple happened to be listed last. The reconciler relies on this to know - // exactly which relations to revoke. + // A user or service account can hold both admin and member. Collect every + // relation for each subject, with duplicates removed, so callers see all of + // them and not just the last one listed. The reconciler needs this to know + // exactly which relations to remove. userRelations := platformRelationsBySubject(relations, schema.UserPrincipal) serviceUserRelations := platformRelationsBySubject(relations, schema.ServiceUserPrincipal) @@ -132,9 +133,9 @@ func (h *ConnectHandler) ListPlatformUsers(ctx context.Context, req *connect.Req }), nil } -// platformRelationsBySubject groups platform relation names (admin/member) by -// subject id for the given principal namespace, deduped and sorted for stable -// output. +// platformRelationsBySubject groups the platform relation names (admin/member) +// by subject id for the given namespace. Duplicates are removed and the result +// is sorted so the output is stable. func platformRelationsBySubject(relations []relation.Relation, namespace string) map[string][]string { sets := map[string]map[string]struct{}{} for _, r := range relations { @@ -158,7 +159,7 @@ func platformRelationsBySubject(relations []relation.Relation, namespace string) return out } -// sortedSubjectIDs returns the subject ids in deterministic order. +// sortedSubjectIDs returns the subject ids in sorted order. func sortedSubjectIDs(m map[string][]string) []string { ids := make([]string, 0, len(m)) for id := range m { @@ -168,9 +169,9 @@ func sortedSubjectIDs(m map[string][]string) []string { return ids } -// stampPlatformRelations records a principal's platform relations in its -// metadata: "relations" carries the full set (consumed by the reconciler) while -// "relation" keeps the first one for backward compatibility. +// stampPlatformRelations records a subject's platform relations in its metadata. +// "relations" holds all of them (used by the reconciler); "relation" keeps the +// first one for backward compatibility. func stampPlatformRelations(md map[string]any, rels []string) { if len(rels) == 0 { return From f31dda957bea5a0d8f902ab068baaaed982c07e7 Mon Sep 17 00:00:00 2001 From: Rohil Surana Date: Wed, 1 Jul 2026 18:04:44 +0530 Subject: [PATCH 04/10] docs(platform): rewrite sudo and audit comments in plain english --- core/serviceuser/service.go | 17 +++++++++-------- core/serviceuser/service_test.go | 16 ++++++++-------- core/user/service.go | 18 +++++++++--------- core/user/service_test.go | 4 ++-- internal/api/v1beta1connect/platform_test.go | 4 ++-- 5 files changed, 30 insertions(+), 29 deletions(-) diff --git a/core/serviceuser/service.go b/core/serviceuser/service.go index fb05f1e71..8cf87fefb 100644 --- a/core/serviceuser/service.go +++ b/core/serviceuser/service.go @@ -453,11 +453,11 @@ func (s Service) Sudo(ctx context.Context, id string, relationName string) error return fmt.Errorf("invalid relation name, possible options are: %s, %s", schema.MemberRelationName, schema.AdminRelationName) } - // Idempotent on the exact relation tuple, not the derived permission: - // `check` is granted by both admin and member, so a permission check would - // skip creating the member tuple for an existing admin. That breaks - // downgrade (Sudo member then UnSudo admin), which UnSudo evaluates at the - // relation level — keep the two symmetric. + // Check the exact relation, not the permission it grants. Running this again + // is safe. Both admin and member grant `check`, so checking the permission + // would skip creating the member relation for someone who is already an admin. + // That breaks a downgrade (Sudo member, then UnSudo admin), because UnSudo + // works at the relation level. Keep the two the same. if ok, err := s.IsSudo(ctx, currentUser.ID, relationName); err != nil { return err } else if ok { @@ -536,9 +536,10 @@ func (s Service) UnSudo(ctx context.Context, id, relationName string) error { return s.recordPlatformAuditRecord(ctx, currentUser, event, relationName) } -// recordPlatformAuditRecord writes an audit record for a platform relation -// (admin or member) grant/revoke on a service user. Actor is left empty so the -// repository enriches it from context (acting principal, or system actor at boot). +// recordPlatformAuditRecord writes an audit record when a platform relation +// (admin or member) is granted or removed on a service user. Actor is left empty, +// so the repository fills it in from the context (the caller, or the system actor +// at boot). func (s Service) recordPlatformAuditRecord(ctx context.Context, su ServiceUser, event pkgAuditRecord.Event, relationName string) error { _, err := s.auditRecordRepository.Create(ctx, models.AuditRecord{ Event: event, diff --git a/core/serviceuser/service_test.go b/core/serviceuser/service_test.go index c4ba9ac33..a03b0fb01 100644 --- a/core/serviceuser/service_test.go +++ b/core/serviceuser/service_test.go @@ -32,8 +32,8 @@ func newTestService(t *testing.T) (*serviceuser.Service, *mocks.Repository, *moc func TestService_Sudo(t *testing.T) { ctx := context.Background() const suID = "550e8400-e29b-41d4-a716-446655440000" - // platformRel builds the platform relation tuple used by IsSudo's check and by - // Create/Delete (field order in the literal is irrelevant for struct equality). + // platformRel builds the platform relation used by IsSudo's check and by + // Create/Delete (field order in the literal doesn't matter for struct equality). platformRel := func(rel string) relation.Relation { return relation.Relation{ Object: relation.Object{ID: schema.PlatformID, Namespace: schema.PlatformNamespace}, @@ -45,8 +45,8 @@ func TestService_Sudo(t *testing.T) { t.Run("grants admin relation and audits the grant", func(t *testing.T) { svc, repo, _, rel, _, audit := newTestService(t) repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, Title: "svc"}, nil) - // Sudo is idempotent on the exact relation (admin), not the superuser - // permission — symmetric with UnSudo. + // Sudo checks the exact relation (admin), not the superuser permission, and + // is safe to run again — the same way UnSudo works. rel.On("CheckPermission", ctx, platformRel(schema.AdminRelationName)).Return(false, nil) rel.On("Create", ctx, platformRel(schema.AdminRelationName)).Return(relation.Relation{}, nil) audit.On("Create", ctx, mock.MatchedBy(func(r models.AuditRecord) bool { @@ -61,8 +61,8 @@ func TestService_Sudo(t *testing.T) { t.Run("grants member relation and audits the grant", func(t *testing.T) { svc, repo, _, rel, _, audit := newTestService(t) repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, Title: "svc"}, nil) - // member is checked at the relation level so an existing admin still gets - // the member tuple created (downgrade path). + // member is checked at the relation level, so an existing admin still gets + // the member relation created (the downgrade path). rel.On("CheckPermission", ctx, platformRel(schema.MemberRelationName)).Return(false, nil) rel.On("Create", ctx, platformRel(schema.MemberRelationName)).Return(relation.Relation{}, nil) audit.On("Create", ctx, mock.MatchedBy(func(r models.AuditRecord) bool { @@ -101,7 +101,7 @@ func TestService_UnSudo(t *testing.T) { } }) - t.Run("admin removal is a no-op when the relation is absent", func(t *testing.T) { + t.Run("admin removal makes no change when the relation is absent", func(t *testing.T) { svc, repo, _, rel, _, _ := newTestService(t) repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, Title: "svc"}, nil) rel.On("CheckPermission", ctx, platformRel(schema.AdminRelationName)).Return(false, nil) @@ -125,7 +125,7 @@ func TestService_UnSudo(t *testing.T) { } }) - t.Run("member removal is a no-op when the relation is absent", func(t *testing.T) { + t.Run("member removal makes no change when the relation is absent", func(t *testing.T) { svc, repo, _, rel, _, _ := newTestService(t) repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, Title: "svc"}, nil) rel.On("CheckPermission", ctx, platformRel(schema.MemberRelationName)).Return(false, nil) diff --git a/core/user/service.go b/core/user/service.go index eb69da35c..be497b50b 100644 --- a/core/user/service.go +++ b/core/user/service.go @@ -188,11 +188,11 @@ func (s Service) Sudo(ctx context.Context, id string, relationName string) error return fmt.Errorf("invalid relation name, possible options are: %s, %s", schema.MemberRelationName, schema.AdminRelationName) } - // Idempotent on the exact relation tuple, not the derived permission: - // `check` is granted by both admin and member, so a permission check would - // skip creating the member tuple for an existing admin. That breaks - // downgrade (Sudo member then UnSudo admin), which UnSudo evaluates at the - // relation level — keep the two symmetric. + // Check the exact relation, not the permission it grants. Running this again + // is safe. Both admin and member grant `check`, so checking the permission + // would skip creating the member relation for someone who is already an admin. + // That breaks a downgrade (Sudo member, then UnSudo admin), because UnSudo + // works at the relation level. Keep the two the same. if ok, err := s.IsSudo(ctx, currentUser.ID, relationName); err != nil { return err } else if ok { @@ -271,10 +271,10 @@ func (s Service) UnSudo(ctx context.Context, id, relationName string) error { return s.recordPlatformAuditRecord(ctx, currentUser, event, relationName) } -// recordPlatformAuditRecord writes an audit record for a platform relation -// (admin or member) grant/revoke. Actor is left empty so the repository enriches -// it from context — the acting principal on request paths, or the system actor -// (uuid.Nil) for boot-time/config-driven changes. +// recordPlatformAuditRecord writes an audit record when a platform relation +// (admin or member) is granted or removed. Actor is left empty, so the repository +// fills it in from the context: the user or account making the request, or the +// system actor (uuid.Nil) for changes at boot or from config. func (s Service) recordPlatformAuditRecord(ctx context.Context, u User, event pkgAuditRecord.Event, relationName string) error { _, err := s.auditRecordRepository.Create(ctx, models.AuditRecord{ Event: event, diff --git a/core/user/service_test.go b/core/user/service_test.go index 71beb277e..0a4e2ff37 100644 --- a/core/user/service_test.go +++ b/core/user/service_test.go @@ -906,7 +906,7 @@ func TestService_UnSudo(t *testing.T) { }, }, { - name: "admin removal is a no-op when user is not a superuser", + name: "admin removal makes no change when user is not a superuser", wantErr: false, args: args{ id: "test-id", @@ -961,7 +961,7 @@ func TestService_UnSudo(t *testing.T) { }, }, { - name: "member removal is a no-op when the relation is absent", + name: "member removal makes no change when the relation is absent", wantErr: false, args: args{ id: "test-id", diff --git a/internal/api/v1beta1connect/platform_test.go b/internal/api/v1beta1connect/platform_test.go index 9f5815b94..ee613239b 100644 --- a/internal/api/v1beta1connect/platform_test.go +++ b/internal/api/v1beta1connect/platform_test.go @@ -17,7 +17,7 @@ import ( func TestHandler_RemovePlatformUser(t *testing.T) { t.Run("removes both admin and member relations for a user", func(t *testing.T) { userSvc := mocks.NewUserService(t) - // both platform relations are stripped; each UnSudo is a no-op for a relation + // both platform relations are removed; each UnSudo does nothing for a relation // the user doesn't hold. userSvc.EXPECT().UnSudo(mock.Anything, "u1", schema.AdminRelationName).Return(nil) userSvc.EXPECT().UnSudo(mock.Anything, "u1", schema.MemberRelationName).Return(nil) @@ -126,7 +126,7 @@ func TestHandler_ListPlatformUsers(t *testing.T) { {Subject: relation.Subject{ID: "u1", Namespace: schema.UserPrincipal}, RelationName: schema.AdminRelationName}, {Subject: relation.Subject{ID: "u1", Namespace: schema.UserPrincipal}, RelationName: schema.MemberRelationName}, }, nil) - // a single, deduped id is fetched even though two tuples were listed. + // a single id is fetched (with duplicates removed) even though two relations were listed. userSvc.EXPECT().GetByIDs(mock.Anything, []string{"u1"}).Return([]user.User{{ID: "u1"}}, nil) h := &ConnectHandler{relationService: relationSvc, userService: userSvc} From 3334397f2ea546787b521820261eb014005f4ef1 Mon Sep 17 00:00:00 2001 From: Rohil Surana Date: Wed, 1 Jul 2026 22:28:19 +0530 Subject: [PATCH 05/10] feat: GitOps platform-user management (bootstrap superuser SA + reconcile CLI) (#1720) --- cmd/reconcile.go | 76 +++++++ cmd/root.go | 1 + cmd/serve.go | 9 +- config/sample.config.yaml | 20 +- core/serviceuser/errors.go | 3 + core/serviceuser/service.go | 8 + core/serviceuser/service_test.go | 28 +++ internal/api/v1beta1connect/platform.go | 8 + internal/api/v1beta1connect/platform_test.go | 15 ++ internal/api/v1beta1connect/serviceuser.go | 41 ++++ .../api/v1beta1connect/serviceuser_test.go | 87 ++++++++ internal/bootstrap/bootstrapuser.go | 203 ++++++++++++++++++ internal/bootstrap/bootstrapuser_test.go | 192 +++++++++++++++++ internal/bootstrap/schema/schema.go | 7 + internal/bootstrap/service.go | 38 ++-- internal/reconcile/platformuser.go | 163 ++++++++++++++ internal/reconcile/platformuser_reconciler.go | 153 +++++++++++++ .../reconcile/platformuser_reconciler_test.go | 151 +++++++++++++ internal/reconcile/platformuser_test.go | 182 ++++++++++++++++ internal/reconcile/reconcile.go | 77 +++++++ internal/reconcile/reconcile_test.go | 36 ++++ .../store/postgres/serviceuser_repository.go | 8 +- test/e2e/testbench/helper.go | 37 ++++ test/e2e/testbench/testbench.go | 14 +- 24 files changed, 1521 insertions(+), 36 deletions(-) create mode 100644 cmd/reconcile.go create mode 100644 internal/bootstrap/bootstrapuser.go create mode 100644 internal/bootstrap/bootstrapuser_test.go create mode 100644 internal/reconcile/platformuser.go create mode 100644 internal/reconcile/platformuser_reconciler.go create mode 100644 internal/reconcile/platformuser_reconciler_test.go create mode 100644 internal/reconcile/platformuser_test.go create mode 100644 internal/reconcile/reconcile.go create mode 100644 internal/reconcile/reconcile_test.go diff --git a/cmd/reconcile.go b/cmd/reconcile.go new file mode 100644 index 000000000..49c49348a --- /dev/null +++ b/cmd/reconcile.go @@ -0,0 +1,76 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/MakeNowJust/heredoc" + "github.com/raystack/frontier/internal/reconcile" + cli "github.com/spf13/cobra" +) + +func ReconcileCommand(cliConfig *Config) *cli.Command { + var ( + filePath string + dryRun bool + header string + ) + cmd := &cli.Command{ + Use: "reconcile", + Short: "Reconcile platform configuration to a desired-state file", + Long: heredoc.Doc(` + Make platform resources match a desired-state YAML file, through the admin API. + + Supports the PlatformUser kind (platform admins and members) for now. The file + decides who has access: anyone listed is added, anyone not listed is removed. + Log in as a superuser (for example the bootstrap service account) with --header. + `), + Example: heredoc.Doc(` + $ frontier reconcile -f platform-users.yaml --dry-run -H "Authorization:Basic " + $ frontier reconcile -f platform-users.yaml -H "Authorization:Basic " + `), + Annotations: map[string]string{ + "group": "core", + "client": "true", + }, + RunE: func(cmd *cli.Command, args []string) error { + data, err := os.ReadFile(filePath) + if err != nil { + return fmt.Errorf("read desired-state file: %w", err) + } + adminClient, err := createAdminClient(cliConfig.Host) + if err != nil { + return err + } + registry := map[string]reconcile.Reconciler{ + reconcile.KindPlatformUser: reconcile.NewPlatformUserReconciler(adminClient, header), + } + reports, runErr := reconcile.Run(cmd.Context(), registry, data, dryRun) + for _, rep := range reports { + printReconcileReport(cmd, rep) + } + return runErr + }, + } + cmd.Flags().StringVarP(&filePath, "file", "f", "", "Path to the desired-state YAML file") + cmd.MarkFlagRequired("file") + cmd.Flags().BoolVar(&dryRun, "dry-run", false, "Print the plan without applying changes") + cmd.Flags().StringVarP(&header, "header", "H", "", "Header : for auth, e.g. 'Authorization:Basic '") + bindFlagsFromClientConfig(cmd) + return cmd +} + +func printReconcileReport(cmd *cli.Command, rep reconcile.Report) { + if len(rep.Planned) == 0 { + cmd.Printf("%s: no changes\n", rep.Kind) + return + } + verb, count := "applied", rep.Applied + if rep.DryRun { + verb, count = "planned", len(rep.Planned) + } + cmd.Printf("%s (%s %d):\n", rep.Kind, verb, count) + for _, p := range rep.Planned { + cmd.Printf(" - %s\n", p) + } +} diff --git a/cmd/root.go b/cmd/root.go index 8d1fb6180..552b811a3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -49,6 +49,7 @@ func New(cliConfig *Config) *cli.Command { cmd.AddCommand(PermissionCommand(cliConfig)) cmd.AddCommand(PolicyCommand(cliConfig)) cmd.AddCommand(SeedCommand(cliConfig)) + cmd.AddCommand(ReconcileCommand(cliConfig)) cmd.AddCommand(configCommand()) cmd.AddCommand(versionCommand()) cmd.AddCommand(PreferencesCommand(cliConfig)) diff --git a/cmd/serve.go b/cmd/serve.go index 4982a6dcb..3bbf77820 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -212,8 +212,9 @@ func StartServer(logger *slog.Logger, cfg *config.Frontier) error { if err = deps.BootstrapService.MigrateRoles(ctx); err != nil { return err } - // promote normal users to superusers - if err = deps.BootstrapService.MakeSuperUsers(ctx); err != nil { + // ensure the config-seeded bootstrap superuser service account (for automation/GitOps). + // all other platform-user management is handled out-of-band via the GitOps reconcile flow. + if err = deps.BootstrapService.EnsureBootstrapSuperUser(ctx); err != nil { return err } @@ -569,7 +570,6 @@ func buildAPIDependencies( namespaceService, roleService, permissionService, - userService, authzSchemaRepository, relationService, policyService, @@ -577,6 +577,9 @@ func buildAPIDependencies( cfg.App.PAT.DeniedPermissionsSet(), planService, planBlobRepository, + svUserRepo, + scUserCredRepo, + serviceUserService, ) cascadeDeleter := deleter.NewCascadeDeleter(organizationService, projectService, resourceService, diff --git a/config/sample.config.yaml b/config/sample.config.yaml index a90840e71..043451f8e 100644 --- a/config/sample.config.yaml +++ b/config/sample.config.yaml @@ -179,12 +179,20 @@ app: # platform level administration admin: - # Email list of users which needs to be converted as superusers - # if the user is already present in the system, it is promoted to su - # if not, a new account is created with provided email id and promoted to su. - # UUIDs/slugs of existing users can also be provided instead of email ids - # but in that case a new user will not be created. - users: [] + # bootstrap seeds a superuser SERVICE ACCOUNT from config (a username/password-style + # client_id + client_secret), so automation like the GitOps reconcile flow always has + # a superuser to log in as. This means you don't need an existing superuser to create + # the first one. The account is created and promoted to superuser on every boot (safe + # to run again), and the secret is rotated if you change it here. Log in with: + # Authorization: Basic base64(client_id:client_secret). + # Leave client_id/client_secret empty to turn this off — but then no superuser is + # seeded, and since granting one needs a superuser, none can be created via the API + # either. client_id must be a UUID (it is the service-account credential id); generate + # one (e.g. uuidgen) and keep it stable. client_secret is your chosen password. + bootstrap: + client_id: "" + client_secret: "" + # title: "GitOps Bootstrap Superuser" # smtp configuration for sending emails mailer: smtp_host: smtp.example.com diff --git a/core/serviceuser/errors.go b/core/serviceuser/errors.go index 2ad4336a7..ca463ec51 100644 --- a/core/serviceuser/errors.go +++ b/core/serviceuser/errors.go @@ -11,4 +11,7 @@ var ( ErrConflict = errors.New("service user already exist") ErrEmptyKey = errors.New("empty key") ErrDisabled = errors.New("service user is disabled") + // ErrProtected marks a server-managed service user (the config-bootstrapped + // bootstrap SA) whose credentials must not be mutated via the API. + ErrProtected = errors.New("service user is protected") ) diff --git a/core/serviceuser/service.go b/core/serviceuser/service.go index 8cf87fefb..f853451e9 100644 --- a/core/serviceuser/service.go +++ b/core/serviceuser/service.go @@ -254,6 +254,14 @@ func (s Service) GetKey(ctx context.Context, credID string) (Credential, error) } func (s Service) DeleteKey(ctx context.Context, credID string) error { + // Never let a credential/key/token of the config-bootstrapped bootstrap SA be + // deleted via the API — it is server-managed (rotated at boot). Boot rotation + // deletes via the repository directly, so it is unaffected by this guard. + if cred, err := s.credRepo.Get(ctx, credID); err == nil && cred.ServiceUserID == schema.BootstrapServiceUserID { + s.log.WarnContext(ctx, "refused deleting a credential of the bootstrap superuser service account", + "serviceuser_id", cred.ServiceUserID, "credential_id", credID) + return ErrProtected + } return s.credRepo.Delete(ctx, credID) } diff --git a/core/serviceuser/service_test.go b/core/serviceuser/service_test.go index a03b0fb01..560a75392 100644 --- a/core/serviceuser/service_test.go +++ b/core/serviceuser/service_test.go @@ -345,3 +345,31 @@ func TestService_ListByOrg(t *testing.T) { }) } } + +func TestService_DeleteKey_BootstrapProtected(t *testing.T) { + ctx := context.Background() + const credID = "cred-1" + + t.Run("refuses deleting a credential owned by the bootstrap SA", func(t *testing.T) { + svc, _, credRepo, _, _, _ := newTestService(t) + credRepo.On("Get", ctx, credID).Return( + serviceuser.Credential{ID: credID, ServiceUserID: schema.BootstrapServiceUserID}, nil) + + err := svc.DeleteSecret(ctx, credID) // funnels through DeleteKey + if !errors.Is(err, serviceuser.ErrProtected) { + t.Fatalf("want ErrProtected, got %v", err) + } + credRepo.AssertNotCalled(t, "Delete", mock.Anything, mock.Anything) + }) + + t.Run("deletes a credential of a normal service user", func(t *testing.T) { + svc, _, credRepo, _, _, _ := newTestService(t) + credRepo.On("Get", ctx, credID).Return( + serviceuser.Credential{ID: credID, ServiceUserID: "11111111-2222-3333-4444-555555555555"}, nil) + credRepo.On("Delete", ctx, credID).Return(nil) + + if err := svc.DeleteToken(ctx, credID); err != nil { // also funnels through DeleteKey + t.Fatalf("unexpected error: %v", err) + } + }) +} diff --git a/internal/api/v1beta1connect/platform.go b/internal/api/v1beta1connect/platform.go index 3eec51c6b..bdba5e90f 100644 --- a/internal/api/v1beta1connect/platform.go +++ b/internal/api/v1beta1connect/platform.go @@ -3,6 +3,7 @@ package v1beta1connect import ( "context" "fmt" + "log/slog" "sort" "strings" @@ -56,6 +57,13 @@ func (h *ConnectHandler) RemovePlatformUser(ctx context.Context, req *connect.Re } } } else if req.Msg.GetServiceuserId() != "" { + // The server manages the bootstrap SA (it seeds it at boot), so it can't be + // removed through this API. Return the generic permission error and log why. + // The response must not reveal that this id is the protected SA. + if req.Msg.GetServiceuserId() == schema.BootstrapServiceUserID { + slog.WarnContext(ctx, "refused removal of the bootstrap superuser service account", "service_user_id", req.Msg.GetServiceuserId()) + return nil, connect.NewError(connect.CodePermissionDenied, ErrUnauthorized) + } for _, relationName := range platformRelations { if err := h.serviceUserService.UnSudo(ctx, req.Msg.GetServiceuserId(), relationName); err != nil { return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("RemovePlatformUser.ServiceUserUnSudo: service_user_id=%s relation=%s: %w", req.Msg.GetServiceuserId(), relationName, err)) diff --git a/internal/api/v1beta1connect/platform_test.go b/internal/api/v1beta1connect/platform_test.go index ee613239b..5f999b638 100644 --- a/internal/api/v1beta1connect/platform_test.go +++ b/internal/api/v1beta1connect/platform_test.go @@ -43,6 +43,21 @@ func TestHandler_RemovePlatformUser(t *testing.T) { assert.NotNil(t, resp) }) + t.Run("refuses to remove the bootstrap superuser service account", func(t *testing.T) { + serviceUserSvc := mocks.NewServiceUserService(t) + // the target is the well-known bootstrap SA -> reject before any UnSudo. + h := &ConnectHandler{serviceUserService: serviceUserSvc} + resp, err := h.RemovePlatformUser(context.Background(), connect.NewRequest(&frontierv1beta1.RemovePlatformUserRequest{ + ServiceuserId: schema.BootstrapServiceUserID, + })) + assert.Error(t, err) + assert.Nil(t, resp) + assert.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err)) + // must not reveal that this id is the protected bootstrap SA. + assert.NotContains(t, err.Error(), "bootstrap") + serviceUserSvc.AssertNotCalled(t, "UnSudo", mock.Anything, mock.Anything, mock.Anything) + }) + t.Run("removes only the specified relation when relation is set", func(t *testing.T) { userSvc := mocks.NewUserService(t) // only the admin relation is stripped; an UnSudo for member would be an diff --git a/internal/api/v1beta1connect/serviceuser.go b/internal/api/v1beta1connect/serviceuser.go index 653297be3..3731239a5 100644 --- a/internal/api/v1beta1connect/serviceuser.go +++ b/internal/api/v1beta1connect/serviceuser.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "connectrpc.com/connect" "github.com/lestrrat-go/jwx/v2/jwk" @@ -168,11 +169,31 @@ func (h *ConnectHandler) CreateServiceUser(ctx context.Context, request *connect }), nil } +// errBootstrapSAImmutable returns a PermissionDenied error when id is the bootstrap +// SA (its fixed id). That account is managed only through app.admin.bootstrap at +// boot. The API must never delete it or create credentials, keys, or tokens for it. +// Doing so would leave a superuser way in that config can't rotate away. Not even +// platform superusers are allowed. +// +// The response is the generic permission error, so it doesn't reveal that this id is +// the protected SA. The specific reason is only logged. +func errBootstrapSAImmutable(ctx context.Context, id string) error { + if id == schema.BootstrapServiceUserID { + slog.WarnContext(ctx, "refused API mutation of the bootstrap superuser service account", "service_user_id", id) + return connect.NewError(connect.CodePermissionDenied, ErrUnauthorized) + } + return nil +} + func (h *ConnectHandler) DeleteServiceUser(ctx context.Context, request *connect.Request[frontierv1beta1.DeleteServiceUserRequest]) (*connect.Response[frontierv1beta1.DeleteServiceUserResponse], error) { errorLogger := NewErrorLogger() serviceUserID := request.Msg.GetId() orgID := request.Msg.GetOrgId() + if err := errBootstrapSAImmutable(ctx, serviceUserID); err != nil { + return nil, err + } + err := h.serviceUserService.Delete(ctx, serviceUserID) if err != nil { errorLogger.LogServiceError(ctx, request, "DeleteServiceUser", err, @@ -198,6 +219,10 @@ func (h *ConnectHandler) CreateServiceUserJWK(ctx context.Context, request *conn serviceUserID := request.Msg.GetId() title := request.Msg.GetTitle() + if err := errBootstrapSAImmutable(ctx, serviceUserID); err != nil { + return nil, err + } + svCred, err := h.serviceUserService.CreateKey(ctx, serviceuser.Credential{ ServiceUserID: serviceUserID, Title: title, @@ -300,6 +325,8 @@ func (h *ConnectHandler) DeleteServiceUserJWK(ctx context.Context, request *conn switch { case err == serviceuser.ErrCredNotExist: return nil, connect.NewError(connect.CodeNotFound, ErrServiceUserCredNotFound) + case errors.Is(err, serviceuser.ErrProtected): + return nil, connect.NewError(connect.CodePermissionDenied, ErrUnauthorized) default: return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("DeleteServiceUserJWK: key_id=%s: %w", keyID, err)) } @@ -312,6 +339,10 @@ func (h *ConnectHandler) CreateServiceUserCredential(ctx context.Context, reques serviceUserID := request.Msg.GetId() title := request.Msg.GetTitle() + if err := errBootstrapSAImmutable(ctx, serviceUserID); err != nil { + return nil, err + } + secret, err := h.serviceUserService.CreateSecret(ctx, serviceuser.Credential{ ServiceUserID: serviceUserID, Title: title, @@ -355,6 +386,9 @@ func (h *ConnectHandler) DeleteServiceUserCredential(ctx context.Context, reques err := h.serviceUserService.DeleteSecret(ctx, secretID) if err != nil { + if errors.Is(err, serviceuser.ErrProtected) { + return nil, connect.NewError(connect.CodePermissionDenied, ErrUnauthorized) + } return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("DeleteServiceUserCredential: secret_id=%s: %w", secretID, err)) } return connect.NewResponse(&frontierv1beta1.DeleteServiceUserCredentialResponse{}), nil @@ -364,6 +398,10 @@ func (h *ConnectHandler) CreateServiceUserToken(ctx context.Context, request *co serviceUserID := request.Msg.GetId() title := request.Msg.GetTitle() + if err := errBootstrapSAImmutable(ctx, serviceUserID); err != nil { + return nil, err + } + secret, err := h.serviceUserService.CreateToken(ctx, serviceuser.Credential{ ServiceUserID: serviceUserID, Title: title, @@ -406,6 +444,9 @@ func (h *ConnectHandler) DeleteServiceUserToken(ctx context.Context, request *co err := h.serviceUserService.DeleteToken(ctx, tokenID) if err != nil { + if errors.Is(err, serviceuser.ErrProtected) { + return nil, connect.NewError(connect.CodePermissionDenied, ErrUnauthorized) + } return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("DeleteServiceUserToken: token_id=%s: %w", tokenID, err)) } return connect.NewResponse(&frontierv1beta1.DeleteServiceUserTokenResponse{}), nil diff --git a/internal/api/v1beta1connect/serviceuser_test.go b/internal/api/v1beta1connect/serviceuser_test.go index be83cfe2c..e15d26e41 100644 --- a/internal/api/v1beta1connect/serviceuser_test.go +++ b/internal/api/v1beta1connect/serviceuser_test.go @@ -1012,6 +1012,93 @@ func TestHandler_ListServiceUserCredentials(t *testing.T) { } } +func TestHandler_BootstrapSAImmutable(t *testing.T) { + // The bootstrap SA (well-known id) must be immutable via the API: no delete and + // no minting of credentials/keys/tokens (which would be a rotation-proof + // superuser backdoor). Each guard must reject before touching the service. + t.Run("DeleteServiceUser is refused", func(t *testing.T) { + su := new(mocks.ServiceUserService) + h := &ConnectHandler{serviceUserService: su} + resp, err := h.DeleteServiceUser(context.Background(), connect.NewRequest(&frontierv1beta1.DeleteServiceUserRequest{ + Id: schema.BootstrapServiceUserID, + })) + assert.Nil(t, resp) + assert.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err)) + assert.NotContains(t, err.Error(), "bootstrap") // must not reveal the protected SA + su.AssertNotCalled(t, "Delete", mock.Anything, mock.Anything) + }) + + t.Run("CreateServiceUserCredential is refused", func(t *testing.T) { + su := new(mocks.ServiceUserService) + h := &ConnectHandler{serviceUserService: su} + resp, err := h.CreateServiceUserCredential(context.Background(), connect.NewRequest(&frontierv1beta1.CreateServiceUserCredentialRequest{ + Id: schema.BootstrapServiceUserID, + })) + assert.Nil(t, resp) + assert.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err)) + assert.NotContains(t, err.Error(), "bootstrap") // must not reveal the protected SA + su.AssertNotCalled(t, "CreateSecret", mock.Anything, mock.Anything) + }) + + t.Run("CreateServiceUserToken is refused", func(t *testing.T) { + su := new(mocks.ServiceUserService) + h := &ConnectHandler{serviceUserService: su} + resp, err := h.CreateServiceUserToken(context.Background(), connect.NewRequest(&frontierv1beta1.CreateServiceUserTokenRequest{ + Id: schema.BootstrapServiceUserID, + })) + assert.Nil(t, resp) + assert.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err)) + assert.NotContains(t, err.Error(), "bootstrap") // must not reveal the protected SA + su.AssertNotCalled(t, "CreateToken", mock.Anything, mock.Anything) + }) + + t.Run("CreateServiceUserJWK is refused", func(t *testing.T) { + su := new(mocks.ServiceUserService) + h := &ConnectHandler{serviceUserService: su} + resp, err := h.CreateServiceUserJWK(context.Background(), connect.NewRequest(&frontierv1beta1.CreateServiceUserJWKRequest{ + Id: schema.BootstrapServiceUserID, + })) + assert.Nil(t, resp) + assert.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err)) + assert.NotContains(t, err.Error(), "bootstrap") // must not reveal the protected SA + su.AssertNotCalled(t, "CreateKey", mock.Anything, mock.Anything) + }) + + // The credential/token/JWK deletes are keyed by secret id, so the service returns + // ErrProtected when the owner is the bootstrap SA; the handler must map it to a + // generic permission_denied without leaking that the SA is protected. + t.Run("DeleteServiceUserCredential is refused (generic) when service reports protected", func(t *testing.T) { + su := new(mocks.ServiceUserService) + su.On("DeleteSecret", mock.Anything, "cred-x").Return(serviceuser.ErrProtected) + h := &ConnectHandler{serviceUserService: su} + resp, err := h.DeleteServiceUserCredential(context.Background(), connect.NewRequest(&frontierv1beta1.DeleteServiceUserCredentialRequest{SecretId: "cred-x"})) + assert.Nil(t, resp) + assert.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err)) + assert.NotContains(t, err.Error(), "bootstrap") + assert.NotContains(t, err.Error(), "protected") + }) + + t.Run("DeleteServiceUserToken is refused (generic) when service reports protected", func(t *testing.T) { + su := new(mocks.ServiceUserService) + su.On("DeleteToken", mock.Anything, "tok-x").Return(serviceuser.ErrProtected) + h := &ConnectHandler{serviceUserService: su} + resp, err := h.DeleteServiceUserToken(context.Background(), connect.NewRequest(&frontierv1beta1.DeleteServiceUserTokenRequest{TokenId: "tok-x"})) + assert.Nil(t, resp) + assert.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err)) + assert.NotContains(t, err.Error(), "bootstrap") + }) + + t.Run("DeleteServiceUserJWK is refused (generic) when service reports protected", func(t *testing.T) { + su := new(mocks.ServiceUserService) + su.On("DeleteKey", mock.Anything, "key-x").Return(serviceuser.ErrProtected) + h := &ConnectHandler{serviceUserService: su} + resp, err := h.DeleteServiceUserJWK(context.Background(), connect.NewRequest(&frontierv1beta1.DeleteServiceUserJWKRequest{KeyId: "key-x"})) + assert.Nil(t, resp) + assert.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err)) + assert.NotContains(t, err.Error(), "bootstrap") + }) +} + func TestHandler_DeleteServiceUserCredential(t *testing.T) { tests := []struct { name string diff --git a/internal/bootstrap/bootstrapuser.go b/internal/bootstrap/bootstrapuser.go new file mode 100644 index 000000000..5a34823d5 --- /dev/null +++ b/internal/bootstrap/bootstrapuser.go @@ -0,0 +1,203 @@ +package bootstrap + +import ( + "context" + "errors" + "fmt" + "log/slog" + "strings" + + "github.com/google/uuid" + "golang.org/x/crypto/bcrypt" + + "github.com/raystack/frontier/core/serviceuser" + "github.com/raystack/frontier/internal/bootstrap/schema" +) + +// bootstrapBcryptCost matches serviceuser.CreateSecret's cost for client secrets. +// It is a var (not a const) so unit tests can lower it to bcrypt.MinCost — cost 14 +// is deliberately expensive and, run under `-race -count 2`, blows the test timeout. +var bootstrapBcryptCost = 14 + +const defaultBootstrapTitle = "GitOps Bootstrap Superuser" + +// SuperUserBootstrapConfig configures a superuser service account seeded from +// config. It gives a username/password-style credential (client_id + client_secret) +// so automation (like the GitOps reconcile flow) always has a superuser to log in as +// at boot. This means you don't need an existing superuser to create the first one. +// You provide the secret, and it is rotated when it changes. Leave client_id and +// client_secret empty to turn this off. +type SuperUserBootstrapConfig struct { + ClientID string `yaml:"client_id" mapstructure:"client_id"` + ClientSecret string `yaml:"client_secret" mapstructure:"client_secret"` + Title string `yaml:"title" mapstructure:"title"` +} + +func (c SuperUserBootstrapConfig) title() string { + if t := strings.TrimSpace(c.Title); t != "" { + return t + } + return defaultBootstrapTitle +} + +// ServiceUserCreator creates a bare service-user row. The bootstrap superuser +// deliberately skips org membership (a platform superuser needs none), so this +// is the repository's Create, not serviceuser.Service.Create. +type ServiceUserCreator interface { + Create(ctx context.Context, su serviceuser.ServiceUser) (serviceuser.ServiceUser, error) + // Delete removes a service user by id. Used to undo a just-created bootstrap + // service user when creating its credential fails, so repeated boot failures + // don't leave behind service-user rows with no credential. + Delete(ctx context.Context, id string) error +} + +// ServiceUserCredentialStore manages client-secret credentials keyed by id. +type ServiceUserCredentialStore interface { + Get(ctx context.Context, id string) (serviceuser.Credential, error) + Create(ctx context.Context, cred serviceuser.Credential) (serviceuser.Credential, error) + Delete(ctx context.Context, id string) error +} + +// SuperUserPromoter grants a platform relation (admin/member) to a principal. +type SuperUserPromoter interface { + Sudo(ctx context.Context, id, relationName string) error +} + +// EnsureBootstrapSuperUser makes sure the configured superuser service account +// exists and is a platform superuser. It is safe to run on every boot. It does +// nothing when the account is not configured. +func (s Service) EnsureBootstrapSuperUser(ctx context.Context) error { + return ensureBootstrapSuperUser(ctx, s.logger, s.adminConfig.Bootstrap, s.suCreator, s.suCredStore, s.suPromoter) +} + +// ensureBootstrapSuperUser holds the testable core. The account lives in the +// platform/nil org (serviceusers.org_id is nullable, no FK) and is created +// without org membership. It decides what to do based on whether the credential +// id (client_id) already exists: +// - not there: create the service user + client-secret credential + promote it; +// - there: rotate the secret if it changed, then make sure the superuser relation is set. +func ensureBootstrapSuperUser( + ctx context.Context, + logger *slog.Logger, + cfg SuperUserBootstrapConfig, + users ServiceUserCreator, + creds ServiceUserCredentialStore, + promoter SuperUserPromoter, +) error { + clientID := strings.TrimSpace(cfg.ClientID) + // Only skip when the config is fully unset. A half-configured bootstrap (only + // one of client_id/client_secret) is a mistake that would quietly skip seeding + // the superuser — fail early instead. + switch { + case clientID == "" && cfg.ClientSecret == "": + return nil + case clientID == "" || cfg.ClientSecret == "": + return errors.New("bootstrap superuser: client_id and client_secret must be set together") + } + // client_id is the service-user credential's id, which is a UUID column; a + // non-UUID would otherwise fail with an opaque SQL error deep in the lookup. + if _, err := uuid.Parse(clientID); err != nil { + return fmt.Errorf("bootstrap superuser: client_id must be a valid uuid: %w", err) + } + + cred, err := creds.Get(ctx, clientID) + switch { + case errors.Is(err, serviceuser.ErrCredNotExist): + return createBootstrapSuperUser(ctx, logger, cfg, clientID, users, creds, promoter) + case err != nil: + return fmt.Errorf("bootstrap superuser: get credential %q: %w", clientID, err) + } + + if bcrypt.CompareHashAndPassword([]byte(cred.SecretHash), []byte(cfg.ClientSecret)) != nil { + if err := rotateBootstrapSecret(ctx, cfg, clientID, cred, creds); err != nil { + return err + } + logger.InfoContext(ctx, "rotated bootstrap superuser secret", "client_id", clientID) + } + return promoteBootstrapSuperUser(ctx, promoter, cred.ServiceUserID) +} + +func createBootstrapSuperUser( + ctx context.Context, + logger *slog.Logger, + cfg SuperUserBootstrapConfig, + clientID string, + users ServiceUserCreator, + creds ServiceUserCredentialStore, + promoter SuperUserPromoter, +) error { + su, err := users.Create(ctx, serviceuser.ServiceUser{ + ID: schema.BootstrapServiceUserID, // fixed id (see schema.BootstrapServiceUserID) + OrgID: schema.PlatformOrgID.String(), + Title: cfg.title(), + }) + if err != nil { + return fmt.Errorf("bootstrap superuser: create service user: %w", err) + } + hash, err := bcrypt.GenerateFromPassword([]byte(cfg.ClientSecret), bootstrapBcryptCost) + if err != nil { + return cleanupOrphanBootstrapSU(ctx, logger, users, su.ID, + fmt.Errorf("bootstrap superuser: hash secret: %w", err)) + } + if _, err := creds.Create(ctx, serviceuser.Credential{ + ID: clientID, + ServiceUserID: su.ID, + Type: serviceuser.ClientSecretCredentialType, + SecretHash: string(hash), + Title: cfg.title(), + }); err != nil { + return cleanupOrphanBootstrapSU(ctx, logger, users, su.ID, + fmt.Errorf("bootstrap superuser: create credential: %w", err)) + } + logger.InfoContext(ctx, "created bootstrap superuser service account", + "client_id", clientID, "serviceuser_id", su.ID) + return promoteBootstrapSuperUser(ctx, promoter, su.ID) +} + +// cleanupOrphanBootstrapSU tries to delete a service user that was just created +// when a later step (hashing, or creating the credential) fails. Without it the +// row would be left behind with no credential and no superuser grant, and the next +// boot, still finding no credential, would create another one. The original error +// is always returned; a failed cleanup is only logged (the next boot retries anyway). +func cleanupOrphanBootstrapSU(ctx context.Context, logger *slog.Logger, users ServiceUserCreator, suID string, cause error) error { + if err := users.Delete(ctx, suID); err != nil { + logger.WarnContext(ctx, "failed to roll back orphan bootstrap service user", + "serviceuser_id", suID, "err", err.Error()) + } + return cause +} + +// rotateBootstrapSecret replaces the stored secret. The credential repo has no +// update, so delete + recreate with the same id (a brief gap at boot is fine). +func rotateBootstrapSecret( + ctx context.Context, + cfg SuperUserBootstrapConfig, + clientID string, + cred serviceuser.Credential, + creds ServiceUserCredentialStore, +) error { + hash, err := bcrypt.GenerateFromPassword([]byte(cfg.ClientSecret), bootstrapBcryptCost) + if err != nil { + return fmt.Errorf("bootstrap superuser: hash secret: %w", err) + } + if err := creds.Delete(ctx, clientID); err != nil { + return fmt.Errorf("bootstrap superuser: rotate (delete): %w", err) + } + if _, err := creds.Create(ctx, serviceuser.Credential{ + ID: clientID, + ServiceUserID: cred.ServiceUserID, + Type: serviceuser.ClientSecretCredentialType, + SecretHash: string(hash), + Title: cred.Title, + }); err != nil { + return fmt.Errorf("bootstrap superuser: rotate (create): %w", err) + } + return nil +} + +func promoteBootstrapSuperUser(ctx context.Context, promoter SuperUserPromoter, suID string) error { + if err := promoter.Sudo(ctx, suID, schema.AdminRelationName); err != nil { + return fmt.Errorf("bootstrap superuser: promote %q: %w", suID, err) + } + return nil +} diff --git a/internal/bootstrap/bootstrapuser_test.go b/internal/bootstrap/bootstrapuser_test.go new file mode 100644 index 000000000..114f75835 --- /dev/null +++ b/internal/bootstrap/bootstrapuser_test.go @@ -0,0 +1,192 @@ +package bootstrap + +import ( + "context" + "errors" + "io" + "log/slog" + "testing" + + "golang.org/x/crypto/bcrypt" + + "github.com/raystack/frontier/core/serviceuser" + "github.com/raystack/frontier/internal/bootstrap/schema" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type mockSUCreator struct{ mock.Mock } + +func (m *mockSUCreator) Create(ctx context.Context, su serviceuser.ServiceUser) (serviceuser.ServiceUser, error) { + args := m.Called(ctx, su) + return args.Get(0).(serviceuser.ServiceUser), args.Error(1) +} + +func (m *mockSUCreator) Delete(ctx context.Context, id string) error { + return m.Called(ctx, id).Error(0) +} + +type mockCredStore struct{ mock.Mock } + +func (m *mockCredStore) Get(ctx context.Context, id string) (serviceuser.Credential, error) { + args := m.Called(ctx, id) + return args.Get(0).(serviceuser.Credential), args.Error(1) +} + +func (m *mockCredStore) Create(ctx context.Context, cred serviceuser.Credential) (serviceuser.Credential, error) { + args := m.Called(ctx, cred) + return args.Get(0).(serviceuser.Credential), args.Error(1) +} + +func (m *mockCredStore) Delete(ctx context.Context, id string) error { + return m.Called(ctx, id).Error(0) +} + +type mockSUPromoter struct{ mock.Mock } + +func (m *mockSUPromoter) Sudo(ctx context.Context, id, relationName string) error { + return m.Called(ctx, id, relationName).Error(0) +} + +func bcryptHash(t *testing.T, secret string) string { + t.Helper() + h, err := bcrypt.GenerateFromPassword([]byte(secret), bootstrapBcryptCost) + if err != nil { + t.Fatalf("hash: %v", err) + } + return string(h) +} + +func TestEnsureBootstrapSuperUser(t *testing.T) { + // Cost 14 is intentionally slow; under `-race -count 2` even a handful of + // hash/compare calls blow the 150s test timeout (measured ~151s). Behaviour is + // identical at any cost, so run the unit test at the minimum. + orig := bootstrapBcryptCost + bootstrapBcryptCost = bcrypt.MinCost + t.Cleanup(func() { bootstrapBcryptCost = orig }) + + ctx := context.Background() + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + const clientID = "f47ac10b-58cc-4372-a567-0e02b2c3d479" + + t.Run("does nothing when not configured", func(t *testing.T) { + users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + + assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, SuperUserBootstrapConfig{}, users, creds, prom)) + creds.AssertNotCalled(t, "Get", mock.Anything, mock.Anything) + users.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) + prom.AssertNotCalled(t, "Sudo", mock.Anything, mock.Anything, mock.Anything) + }) + + t.Run("rejects a half-configured bootstrap (one of id/secret missing)", func(t *testing.T) { + users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + + assert.Error(t, ensureBootstrapSuperUser(ctx, logger, SuperUserBootstrapConfig{ClientID: clientID}, users, creds, prom)) + assert.Error(t, ensureBootstrapSuperUser(ctx, logger, SuperUserBootstrapConfig{ClientSecret: "s3cret"}, users, creds, prom)) + creds.AssertNotCalled(t, "Get", mock.Anything, mock.Anything) + users.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) + }) + + t.Run("rejects a non-uuid client_id", func(t *testing.T) { + users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + cfg := SuperUserBootstrapConfig{ClientID: "not-a-uuid", ClientSecret: "s3cret"} + + assert.Error(t, ensureBootstrapSuperUser(ctx, logger, cfg, users, creds, prom)) + creds.AssertNotCalled(t, "Get", mock.Anything, mock.Anything) + }) + + t.Run("creates service user + credential + promotes when absent", func(t *testing.T) { + users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} + + creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{}, serviceuser.ErrCredNotExist) + users.On("Create", mock.Anything, mock.MatchedBy(func(su serviceuser.ServiceUser) bool { + return su.ID == schema.BootstrapServiceUserID && + su.OrgID == schema.PlatformOrgID.String() && su.Title == defaultBootstrapTitle + })).Return(serviceuser.ServiceUser{ID: "su-id"}, nil) + // Capture the credential and verify the hash once after the call rather + // than inside the matcher: testify re-invokes argument matchers, and bcrypt + // is costly enough there to stall CI. + var created serviceuser.Credential + creds.On("Create", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { created = args.Get(1).(serviceuser.Credential) }). + Return(serviceuser.Credential{ID: clientID}, nil) + prom.On("Sudo", mock.Anything, "su-id", schema.AdminRelationName).Return(nil) + + assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, cfg, users, creds, prom)) + users.AssertExpectations(t) + creds.AssertExpectations(t) + prom.AssertExpectations(t) + + assert.Equal(t, clientID, created.ID) + assert.Equal(t, "su-id", created.ServiceUserID) + assert.Equal(t, serviceuser.ClientSecretCredentialType, created.Type) + assert.NoError(t, bcrypt.CompareHashAndPassword([]byte(created.SecretHash), []byte("s3cret"))) + }) + + t.Run("ensures superuser without rotating when the secret matches", func(t *testing.T) { + users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} + + creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{ + ID: clientID, ServiceUserID: "su-id", SecretHash: bcryptHash(t, "s3cret"), + }, nil) + prom.On("Sudo", mock.Anything, "su-id", schema.AdminRelationName).Return(nil) + + assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, cfg, users, creds, prom)) + prom.AssertExpectations(t) + creds.AssertNotCalled(t, "Delete", mock.Anything, mock.Anything) + creds.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) + users.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) + }) + + t.Run("rotates the secret when it changed, then ensures superuser", func(t *testing.T) { + users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "new-secret"} + + creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{ + ID: clientID, ServiceUserID: "su-id", SecretHash: bcryptHash(t, "old-secret"), Title: "t", + }, nil) + creds.On("Delete", mock.Anything, clientID).Return(nil) + var rotated serviceuser.Credential + creds.On("Create", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { rotated = args.Get(1).(serviceuser.Credential) }). + Return(serviceuser.Credential{ID: clientID}, nil) + prom.On("Sudo", mock.Anything, "su-id", schema.AdminRelationName).Return(nil) + + assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, cfg, users, creds, prom)) + creds.AssertExpectations(t) + prom.AssertExpectations(t) + users.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) + + assert.Equal(t, clientID, rotated.ID) + assert.Equal(t, "su-id", rotated.ServiceUserID) + assert.NoError(t, bcrypt.CompareHashAndPassword([]byte(rotated.SecretHash), []byte("new-secret"))) + }) + + t.Run("rolls back the service user when credential creation fails", func(t *testing.T) { + users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} + + creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{}, serviceuser.ErrCredNotExist) + users.On("Create", mock.Anything, mock.Anything).Return(serviceuser.ServiceUser{ID: "su-id"}, nil) + creds.On("Create", mock.Anything, mock.Anything).Return(serviceuser.Credential{}, errors.New("db down")) + users.On("Delete", mock.Anything, "su-id").Return(nil) + + assert.Error(t, ensureBootstrapSuperUser(ctx, logger, cfg, users, creds, prom)) + users.AssertExpectations(t) // Create + compensating Delete both invoked + creds.AssertExpectations(t) + prom.AssertNotCalled(t, "Sudo", mock.Anything, mock.Anything, mock.Anything) + }) + + t.Run("aborts on a non-not-found credential lookup error", func(t *testing.T) { + users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} + + creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{}, errors.New("db down")) + + assert.Error(t, ensureBootstrapSuperUser(ctx, logger, cfg, users, creds, prom)) + users.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) + prom.AssertNotCalled(t, "Sudo", mock.Anything, mock.Anything, mock.Anything) + }) +} diff --git a/internal/bootstrap/schema/schema.go b/internal/bootstrap/schema/schema.go index 21b59937e..0b671c377 100644 --- a/internal/bootstrap/schema/schema.go +++ b/internal/bootstrap/schema/schema.go @@ -17,6 +17,13 @@ const ( // Global IDs PlatformID = "platform" + // BootstrapServiceUserID is the fixed service-user id of the bootstrap superuser + // SA from config (app.admin.bootstrap). It is deliberately not uuid.Nil: uuid.Nil + // is reserved for the audit "system" actor, so a real principal must not use it. + // Because this id is fixed, the platform API can refuse to remove the SA and the + // reconciler can skip it, with no lookup needed. + BootstrapServiceUserID = "00000000-0000-0000-0000-000000000001" + // namespace PlatformNamespace = "app/platform" OrganizationNamespace = "app/organization" diff --git a/internal/bootstrap/service.go b/internal/bootstrap/service.go index af97e16a2..12407a65d 100644 --- a/internal/bootstrap/service.go +++ b/internal/bootstrap/service.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "log/slog" - "strings" "github.com/raystack/frontier/billing/plan" @@ -44,10 +43,6 @@ type RelationService interface { Delete(ctx context.Context, rel relation.Relation) error } -type UserService interface { - Sudo(ctx context.Context, id string, relationName string) error -} - type FileService interface { GetDefinition(ctx context.Context) (*schema.ServiceDefinition, error) } @@ -85,9 +80,11 @@ type ServiceUserBackfiller interface { // AdminConfig is platform administration configuration type AdminConfig struct { - // Users are a list of email-ids/uuids which needs to be promoted as superusers - // if email is provided and user doesn't exist, user is created by default - Users []string `yaml:"users" mapstructure:"users"` + // Bootstrap seeds a superuser service account from config (client_id/secret) so + // automation (e.g. GitOps) always has a superuser to log in as. This means you + // don't need an existing superuser to create the first one. Everything else about + // platform users is managed separately, through the GitOps reconcile flow. + Bootstrap SuperUserBootstrapConfig `yaml:"bootstrap" mapstructure:"bootstrap"` } type Service struct { @@ -98,12 +95,15 @@ type Service struct { roleService RoleService permissionService PermissionService authzEngine AuthzEngine - userService UserService relationService RelationService policyService PolicyService serviceuserRepo ServiceUserBackfiller patDeniedPerms map[string]struct{} + suCreator ServiceUserCreator + suCredStore ServiceUserCredentialStore + suPromoter SuperUserPromoter + planService PlanService planLocalRepo BillingPlanRepository } @@ -115,7 +115,6 @@ func NewBootstrapService( namespaceService NamespaceService, roleService RoleService, actionService PermissionService, - userService UserService, authzEngine AuthzEngine, relationService RelationService, policyService PolicyService, @@ -123,6 +122,9 @@ func NewBootstrapService( patDeniedPerms map[string]struct{}, planService PlanService, planLocalRepo BillingPlanRepository, + suCreator ServiceUserCreator, + suCredStore ServiceUserCredentialStore, + suPromoter SuperUserPromoter, ) *Service { return &Service{ logger: logger, @@ -131,7 +133,6 @@ func NewBootstrapService( namespaceService: namespaceService, roleService: roleService, permissionService: actionService, - userService: userService, authzEngine: authzEngine, planService: planService, planLocalRepo: planLocalRepo, @@ -139,6 +140,9 @@ func NewBootstrapService( policyService: policyService, serviceuserRepo: serviceuserRepo, patDeniedPerms: patDeniedPerms, + suCreator: suCreator, + suCredStore: suCredStore, + suPromoter: suPromoter, } } @@ -258,18 +262,6 @@ func filterDefaultAppNamespacePermissions(permissions []schema.ResourcePermissio return filteredPermissions } -// MakeSuperUsers promote ordinary users to superuser -func (s Service) MakeSuperUsers(ctx context.Context) error { - for _, userID := range s.adminConfig.Users { - userID = strings.TrimSpace(userID) - slog.DebugContext(ctx, "promoting user to superuser", "user_id", userID) - if err := s.userService.Sudo(ctx, userID, schema.AdminRelationName); err != nil { - return err - } - } - return nil -} - // MigrateRoles migrate predefined roles to org func (s Service) MigrateRoles(ctx context.Context) error { var err error diff --git a/internal/reconcile/platformuser.go b/internal/reconcile/platformuser.go new file mode 100644 index 000000000..f1b6cebaa --- /dev/null +++ b/internal/reconcile/platformuser.go @@ -0,0 +1,163 @@ +package reconcile + +import ( + "fmt" + "strings" + + "github.com/raystack/frontier/internal/bootstrap/schema" +) + +// KindPlatformUser is the desired-state document kind for platform users. +const KindPlatformUser = "PlatformUser" + +const ( + principalTypeUser = "user" + principalTypeServiceUser = "serviceuser" +) + +// PlatformUserSpec is one desired platform-user entry from the YAML spec. +// Relation is the platform relation to grant — admin (-> superuser) or member +// (-> check). It is deliberately "relation", not "role": "role" is a distinct +// RBAC concept in Frontier, whereas these are SpiceDB relation names. +type PlatformUserSpec struct { + Type string `yaml:"type"` // "user" | "serviceuser" + Ref string `yaml:"ref"` // email or uuid for a user; id for a service user + Relation string `yaml:"relation"` // "admin" | "member" +} + +// platformPrincipal is the current state of one platform principal (from ListPlatformUsers), +// with the set of platform relations it currently holds. +type platformPrincipal struct { + Type string + ID string + Email string // users only + Relations map[string]struct{} +} + +// opAction is an apply operation kind. +type opAction string + +const ( + opAdd opAction = "add" + opRemove opAction = "remove" +) + +// Op is a single planned change to one (principal, relation). "add" grants the +// relation; "remove" takes away just that one relation. Ref holds the desired +// entry's ref for an add (email or id), and the current principal's id for a remove. +type Op struct { + Action opAction + Type string + Ref string + Relation string +} + +func (o Op) String() string { + if o.Action == opRemove { + return fmt.Sprintf("remove %s %s (%s)", o.Type, o.Ref, o.Relation) + } + return fmt.Sprintf("add %s %s as %s", o.Type, o.Ref, o.Relation) +} + +// principalIDs maps the op's ref onto the request id fields: a user ref fills +// user_id, anything else fills serviceuser_id. Exactly one is non-empty. +func (o Op) principalIDs() (userID, serviceUserID string) { + if o.Type == principalTypeUser { + return o.Ref, "" + } + return "", o.Ref +} + +func validateSpec(s PlatformUserSpec) error { + switch s.Type { + case principalTypeUser, principalTypeServiceUser: + default: + return fmt.Errorf("invalid type %q (want %q or %q)", s.Type, principalTypeUser, principalTypeServiceUser) + } + switch s.Relation { + case schema.AdminRelationName, schema.MemberRelationName: + default: + return fmt.Errorf("invalid relation %q (want %q or %q)", s.Relation, schema.AdminRelationName, schema.MemberRelationName) + } + if strings.TrimSpace(s.Ref) == "" { + return fmt.Errorf("empty ref") + } + // The server manages the bootstrap SA (it seeds it at boot and blocks removal), + // so it must not be reconciled. Reject it on the desired side too, not just skip + // it on the current side. + if s.Type == principalTypeServiceUser && strings.TrimSpace(s.Ref) == schema.BootstrapServiceUserID { + return fmt.Errorf("ref %q is the bootstrap service account, which the server manages and cannot be reconciled", s.Ref) + } + return nil +} + +// specMatchesPrincipal reports whether a desired spec refers to a current principal. +// Users match by id or email; service users by id. +func specMatchesPrincipal(s PlatformUserSpec, p platformPrincipal) bool { + if s.Type != p.Type { + return false + } + if s.Ref == p.ID { + return true + } + return p.Type == principalTypeUser && s.Ref != "" && strings.EqualFold(s.Ref, p.Email) +} + +// platformRelationOrder gives operations a stable, fixed order. +var platformRelationOrder = []string{schema.AdminRelationName, schema.MemberRelationName} + +// diffPlatformUsers works out the changes needed to make the current platform +// principals match the desired spec, one (principal, relation) at a time. A current +// relation that is no longer wanted is removed. A wanted relation that is missing is +// added. A desired entry that matches no current principal is new, so it is added. +// The output order is fixed. +func diffPlatformUsers(desired []PlatformUserSpec, current []platformPrincipal) ([]Op, error) { + for _, s := range desired { + if err := validateSpec(s); err != nil { + return nil, fmt.Errorf("invalid platform-user spec %+v: %w", s, err) + } + } + + // Collect adds and removes separately so adds come first (see the return). + // Apply runs in order, so doing adds before removes means a relation change + // (e.g. admin -> member) never leaves someone with no platform access if a + // later op fails. + var adds, removes []Op + matched := make([]bool, len(desired)) + + for _, p := range current { + want := map[string]struct{}{} + for i, s := range desired { + if specMatchesPrincipal(s, p) { + matched[i] = true + want[s.Relation] = struct{}{} + } + } + for _, rel := range platformRelationOrder { + _, has := p.Relations[rel] + _, wanted := want[rel] + switch { + case wanted && !has: + adds = append(adds, Op{Action: opAdd, Type: p.Type, Ref: p.ID, Relation: rel}) + case has && !wanted: + removes = append(removes, Op{Action: opRemove, Type: p.Type, Ref: p.ID, Relation: rel}) + } + } + } + + // add desired entries that match no current platform principal + seenNewRef := map[string]struct{}{} + for i, s := range desired { + if matched[i] { + continue + } + key := s.Type + "\x00" + s.Ref + "\x00" + s.Relation + if _, dup := seenNewRef[key]; dup { + continue + } + seenNewRef[key] = struct{}{} + adds = append(adds, Op{Action: opAdd, Type: s.Type, Ref: s.Ref, Relation: s.Relation}) + } + + return append(adds, removes...), nil +} diff --git a/internal/reconcile/platformuser_reconciler.go b/internal/reconcile/platformuser_reconciler.go new file mode 100644 index 000000000..8d592ec02 --- /dev/null +++ b/internal/reconcile/platformuser_reconciler.go @@ -0,0 +1,153 @@ +package reconcile + +import ( + "context" + "fmt" + "strings" + + "connectrpc.com/connect" + "github.com/raystack/frontier/internal/bootstrap/schema" + frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" + "google.golang.org/protobuf/types/known/structpb" + "gopkg.in/yaml.v3" +) + +// PlatformUserAPI is the subset of the admin API the platform-user reconciler needs. +// frontierv1beta1connect.AdminServiceClient satisfies it. +type PlatformUserAPI interface { + ListPlatformUsers(context.Context, *connect.Request[frontierv1beta1.ListPlatformUsersRequest]) (*connect.Response[frontierv1beta1.ListPlatformUsersResponse], error) + AddPlatformUser(context.Context, *connect.Request[frontierv1beta1.AddPlatformUserRequest]) (*connect.Response[frontierv1beta1.AddPlatformUserResponse], error) + RemovePlatformUser(context.Context, *connect.Request[frontierv1beta1.RemovePlatformUserRequest]) (*connect.Response[frontierv1beta1.RemovePlatformUserResponse], error) +} + +// PlatformUserReconciler makes platform admins and members match the desired spec. +type PlatformUserReconciler struct { + client PlatformUserAPI + header string // "key:value" auth header applied to each request (may be empty) +} + +func NewPlatformUserReconciler(client PlatformUserAPI, header string) *PlatformUserReconciler { + return &PlatformUserReconciler{client: client, header: header} +} + +func (r *PlatformUserReconciler) Kind() string { return KindPlatformUser } + +func (r *PlatformUserReconciler) Reconcile(ctx context.Context, spec []byte, dryRun bool) (Report, error) { + var specs []PlatformUserSpec + if err := yaml.Unmarshal(spec, &specs); err != nil { + return Report{}, fmt.Errorf("parse %s spec: %w", KindPlatformUser, err) + } + + current, err := r.fetchCurrent(ctx) + if err != nil { + return Report{}, err + } + + ops, err := diffPlatformUsers(specs, current) + if err != nil { + return Report{}, err + } + + rep := Report{Kind: KindPlatformUser, DryRun: dryRun} + for _, op := range ops { + rep.Planned = append(rep.Planned, op.String()) + } + if dryRun { + return rep, nil + } + for _, op := range ops { + if err := r.apply(ctx, op); err != nil { + return rep, fmt.Errorf("apply [%s]: %w", op, err) + } + rep.Applied++ + } + return rep, nil +} + +func (r *PlatformUserReconciler) fetchCurrent(ctx context.Context) ([]platformPrincipal, error) { + resp, err := r.client.ListPlatformUsers(ctx, authReq(&frontierv1beta1.ListPlatformUsersRequest{}, r.header)) + if err != nil { + return nil, fmt.Errorf("list platform users: %w", err) + } + var current []platformPrincipal + for _, u := range resp.Msg.GetUsers() { + current = append(current, platformPrincipal{ + Type: principalTypeUser, + ID: u.GetId(), + Email: u.GetEmail(), + Relations: relationsFromMetadata(u.GetMetadata()), + }) + } + for _, su := range resp.Msg.GetServiceusers() { + // The bootstrap SA (created from config at boot) has a fixed id. The server + // manages it and blocks removal, so never reconcile it — that way we never + // try to remove it. + if su.GetId() == schema.BootstrapServiceUserID { + continue + } + current = append(current, platformPrincipal{ + Type: principalTypeServiceUser, + ID: su.GetId(), + Relations: relationsFromMetadata(su.GetMetadata()), + }) + } + return current, nil +} + +func (r *PlatformUserReconciler) apply(ctx context.Context, op Op) error { + // exactly one of the two ids is set, by principal type. + userID, serviceUserID := op.principalIDs() + switch op.Action { + case opAdd: + _, err := r.client.AddPlatformUser(ctx, authReq(&frontierv1beta1.AddPlatformUserRequest{ + UserId: userID, ServiceuserId: serviceUserID, Relation: op.Relation, + }, r.header)) + return err + case opRemove: + // remove only op.Relation, not both (proton #489). + _, err := r.client.RemovePlatformUser(ctx, authReq(&frontierv1beta1.RemovePlatformUserRequest{ + UserId: userID, ServiceuserId: serviceUserID, Relation: op.Relation, + }, r.header)) + return err + default: + return fmt.Errorf("unknown op action %q", op.Action) + } +} + +// relationsFromMetadata reads the platform relations that ListPlatformUsers records +// in each principal's metadata. It uses the full "relations" list when present (so +// someone holding both admin and member is handled correctly), and falls back to the +// single "relation" field for backward compatibility. +func relationsFromMetadata(md *structpb.Struct) map[string]struct{} { + rels := map[string]struct{}{} + if md == nil { + return rels + } + fields := md.GetFields() + if v, ok := fields["relations"]; ok { + for _, item := range v.GetListValue().GetValues() { + if name := item.GetStringValue(); name != "" { + rels[name] = struct{}{} + } + } + } + if len(rels) == 0 { + if v, ok := fields["relation"]; ok { + if name := v.GetStringValue(); name != "" { + rels[name] = struct{}{} + } + } + } + return rels +} + +// authReq builds a connect request and applies the optional "key:value" auth header. +func authReq[T any](msg *T, header string) *connect.Request[T] { + req := connect.NewRequest(msg) + if header != "" { + if k, v, ok := strings.Cut(header, ":"); ok { + req.Header().Set(strings.TrimSpace(k), strings.TrimSpace(v)) + } + } + return req +} diff --git a/internal/reconcile/platformuser_reconciler_test.go b/internal/reconcile/platformuser_reconciler_test.go new file mode 100644 index 000000000..8bf4f8634 --- /dev/null +++ b/internal/reconcile/platformuser_reconciler_test.go @@ -0,0 +1,151 @@ +package reconcile + +import ( + "context" + "testing" + + "connectrpc.com/connect" + "github.com/raystack/frontier/internal/bootstrap/schema" + frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/structpb" +) + +type fakePlatformUserAPI struct { + users []*frontierv1beta1.User + sus []*frontierv1beta1.ServiceUser + added []*frontierv1beta1.AddPlatformUserRequest + removed []*frontierv1beta1.RemovePlatformUserRequest +} + +func (f *fakePlatformUserAPI) ListPlatformUsers(_ context.Context, _ *connect.Request[frontierv1beta1.ListPlatformUsersRequest]) (*connect.Response[frontierv1beta1.ListPlatformUsersResponse], error) { + return connect.NewResponse(&frontierv1beta1.ListPlatformUsersResponse{Users: f.users, Serviceusers: f.sus}), nil +} + +func (f *fakePlatformUserAPI) AddPlatformUser(_ context.Context, req *connect.Request[frontierv1beta1.AddPlatformUserRequest]) (*connect.Response[frontierv1beta1.AddPlatformUserResponse], error) { + f.added = append(f.added, req.Msg) + return connect.NewResponse(&frontierv1beta1.AddPlatformUserResponse{}), nil +} + +func (f *fakePlatformUserAPI) RemovePlatformUser(_ context.Context, req *connect.Request[frontierv1beta1.RemovePlatformUserRequest]) (*connect.Response[frontierv1beta1.RemovePlatformUserResponse], error) { + f.removed = append(f.removed, req.Msg) + return connect.NewResponse(&frontierv1beta1.RemovePlatformUserResponse{}), nil +} + +func platformUserPB(t *testing.T, id, email, relation string) *frontierv1beta1.User { + t.Helper() + md, err := structpb.NewStruct(map[string]interface{}{"relation": relation}) + if err != nil { + t.Fatalf("struct: %v", err) + } + return &frontierv1beta1.User{Id: id, Email: email, Metadata: md} +} + +// platformUserPBRelations stamps the full "relations" list the way ListPlatformUsers does. +func platformUserPBRelations(t *testing.T, id, email string, relations ...string) *frontierv1beta1.User { + t.Helper() + vals := make([]interface{}, len(relations)) + for i, r := range relations { + vals[i] = r + } + md, err := structpb.NewStruct(map[string]interface{}{"relations": vals}) + if err != nil { + t.Fatalf("struct: %v", err) + } + return &frontierv1beta1.User{Id: id, Email: email, Metadata: md} +} + +// serviceUserPBRelations builds a platform service-user list entry. +func serviceUserPBRelations(t *testing.T, id string, relations ...string) *frontierv1beta1.ServiceUser { + t.Helper() + vals := make([]interface{}, len(relations)) + for i, r := range relations { + vals[i] = r + } + md, err := structpb.NewStruct(map[string]interface{}{"relations": vals}) + if err != nil { + t.Fatalf("struct: %v", err) + } + return &frontierv1beta1.ServiceUser{Id: id, Metadata: md} +} + +func TestPlatformUserReconciler_Reconcile(t *testing.T) { + // desired: one new admin; the existing "drop" admin is absent -> removed. + specYAML := []byte("- {type: user, ref: new@x.com, relation: admin}\n") + + t.Run("applies adds and removes", func(t *testing.T) { + api := &fakePlatformUserAPI{users: []*frontierv1beta1.User{ + platformUserPB(t, "drop-id", "drop@x.com", schema.AdminRelationName), + }} + rep, err := NewPlatformUserReconciler(api, "").Reconcile(context.Background(), specYAML, false) + + assert.NoError(t, err) + assert.Equal(t, 2, rep.Applied) + if assert.Len(t, api.removed, 1) { + assert.Equal(t, "drop-id", api.removed[0].GetUserId()) + assert.Equal(t, schema.AdminRelationName, api.removed[0].GetRelation()) + } + if assert.Len(t, api.added, 1) { + assert.Equal(t, "new@x.com", api.added[0].GetUserId()) + assert.Equal(t, schema.AdminRelationName, api.added[0].GetRelation()) + } + }) + + t.Run("dry-run plans without applying", func(t *testing.T) { + api := &fakePlatformUserAPI{users: []*frontierv1beta1.User{ + platformUserPB(t, "drop-id", "drop@x.com", schema.AdminRelationName), + }} + rep, err := NewPlatformUserReconciler(api, "").Reconcile(context.Background(), specYAML, true) + + assert.NoError(t, err) + assert.True(t, rep.DryRun) + assert.NotEmpty(t, rep.Planned) + assert.Equal(t, 0, rep.Applied) + assert.Empty(t, api.added) + assert.Empty(t, api.removed) + }) + + t.Run("no changes when already matching", func(t *testing.T) { + api := &fakePlatformUserAPI{users: []*frontierv1beta1.User{ + platformUserPB(t, "new-id", "new@x.com", schema.AdminRelationName), + }} + rep, err := NewPlatformUserReconciler(api, "").Reconcile(context.Background(), specYAML, false) + + assert.NoError(t, err) + assert.Empty(t, rep.Planned) + assert.Empty(t, api.added) + assert.Empty(t, api.removed) + }) + + t.Run("never removes the bootstrap service account (fixed id)", func(t *testing.T) { + // an empty desired state would remove everyone, but the bootstrap SA is + // matched by its fixed id and must be left out and untouched. + api := &fakePlatformUserAPI{sus: []*frontierv1beta1.ServiceUser{ + serviceUserPBRelations(t, schema.BootstrapServiceUserID, schema.AdminRelationName), + }} + rep, err := NewPlatformUserReconciler(api, "").Reconcile(context.Background(), []byte("[]\n"), false) + + assert.NoError(t, err) + assert.Empty(t, rep.Planned) + assert.Empty(t, api.added) + assert.Empty(t, api.removed) + }) + + t.Run("strips the extra relation when the principal holds both (relations list)", func(t *testing.T) { + // alice is desired as admin only but currently holds admin + member; + // the reconciler must read both relations and revoke member exactly. + yamlSpec := []byte("- {type: user, ref: alice@x.com, relation: admin}\n") + api := &fakePlatformUserAPI{users: []*frontierv1beta1.User{ + platformUserPBRelations(t, "alice-id", "alice@x.com", schema.AdminRelationName, schema.MemberRelationName), + }} + rep, err := NewPlatformUserReconciler(api, "").Reconcile(context.Background(), yamlSpec, false) + + assert.NoError(t, err) + assert.Equal(t, 1, rep.Applied) + assert.Empty(t, api.added) + if assert.Len(t, api.removed, 1) { + assert.Equal(t, "alice-id", api.removed[0].GetUserId()) + assert.Equal(t, schema.MemberRelationName, api.removed[0].GetRelation()) + } + }) +} diff --git a/internal/reconcile/platformuser_test.go b/internal/reconcile/platformuser_test.go new file mode 100644 index 000000000..cd850b809 --- /dev/null +++ b/internal/reconcile/platformuser_test.go @@ -0,0 +1,182 @@ +package reconcile + +import ( + "testing" + + "github.com/raystack/frontier/internal/bootstrap/schema" + "github.com/stretchr/testify/assert" +) + +func principal(typ, id, email string, relations ...string) platformPrincipal { + rels := make(map[string]struct{}, len(relations)) + for _, r := range relations { + rels[r] = struct{}{} + } + return platformPrincipal{Type: typ, ID: id, Email: email, Relations: rels} +} + +func TestDiffPlatformUsers(t *testing.T) { + admin := schema.AdminRelationName + member := schema.MemberRelationName + + t.Run("no desired, no current -> no ops", func(t *testing.T) { + ops, err := diffPlatformUsers(nil, nil) + assert.NoError(t, err) + assert.Empty(t, ops) + }) + + t.Run("new user is added", func(t *testing.T) { + ops, err := diffPlatformUsers( + []PlatformUserSpec{{Type: "user", Ref: "alice@x.com", Relation: admin}}, + nil, + ) + assert.NoError(t, err) + assert.Equal(t, []Op{{Action: opAdd, Type: "user", Ref: "alice@x.com", Relation: admin}}, ops) + }) + + t.Run("new service user is added", func(t *testing.T) { + ops, err := diffPlatformUsers( + []PlatformUserSpec{{Type: "serviceuser", Ref: "su-1", Relation: member}}, + nil, + ) + assert.NoError(t, err) + assert.Equal(t, []Op{{Action: opAdd, Type: "serviceuser", Ref: "su-1", Relation: member}}, ops) + }) + + t.Run("already-correct principal makes no change (matched by email)", func(t *testing.T) { + ops, err := diffPlatformUsers( + []PlatformUserSpec{{Type: "user", Ref: "alice@x.com", Relation: admin}}, + []platformPrincipal{principal("user", "alice-id", "alice@x.com", admin)}, + ) + assert.NoError(t, err) + assert.Empty(t, ops) + }) + + t.Run("undesired principal is removed", func(t *testing.T) { + ops, err := diffPlatformUsers( + nil, + []platformPrincipal{principal("user", "drop-id", "drop@x.com", admin)}, + ) + assert.NoError(t, err) + assert.Equal(t, []Op{{Action: opRemove, Type: "user", Ref: "drop-id", Relation: admin}}, ops) + }) + + t.Run("relation change adds the new relation before removing the old", func(t *testing.T) { + ops, err := diffPlatformUsers( + []PlatformUserSpec{{Type: "user", Ref: "alice@x.com", Relation: member}}, + []platformPrincipal{principal("user", "alice-id", "alice@x.com", admin)}, + ) + assert.NoError(t, err) + // add-before-remove so a failed apply never leaves the principal access-less. + assert.Equal(t, []Op{ + {Action: opAdd, Type: "user", Ref: "alice-id", Relation: member}, + {Action: opRemove, Type: "user", Ref: "alice-id", Relation: admin}, + }, ops) + }) + + t.Run("mixed: keep one, add one, remove one", func(t *testing.T) { + ops, err := diffPlatformUsers( + []PlatformUserSpec{ + {Type: "user", Ref: "keep@x.com", Relation: admin}, // already correct + {Type: "user", Ref: "new@x.com", Relation: member}, // new -> add + }, + []platformPrincipal{ + principal("user", "keep-id", "keep@x.com", admin), // matches keep -> no change + principal("user", "drop-id", "drop@x.com", admin), // not desired -> remove + }, + ) + assert.NoError(t, err) + // adds first, then removes (across principals). + assert.Equal(t, []Op{ + {Action: opAdd, Type: "user", Ref: "new@x.com", Relation: member}, + {Action: opRemove, Type: "user", Ref: "drop-id", Relation: admin}, + }, ops) + }) + + t.Run("strips the extra relation when a principal holds both but only one is desired", func(t *testing.T) { + ops, err := diffPlatformUsers( + []PlatformUserSpec{{Type: "user", Ref: "alice@x.com", Relation: admin}}, + []platformPrincipal{principal("user", "alice-id", "alice@x.com", admin, member)}, + ) + assert.NoError(t, err) + assert.Equal(t, []Op{{Action: opRemove, Type: "user", Ref: "alice-id", Relation: member}}, ops) + }) + + t.Run("keeps both relations when both are desired", func(t *testing.T) { + ops, err := diffPlatformUsers( + []PlatformUserSpec{ + {Type: "user", Ref: "alice@x.com", Relation: admin}, + {Type: "user", Ref: "alice@x.com", Relation: member}, + }, + []platformPrincipal{principal("user", "alice-id", "alice@x.com", admin, member)}, + ) + assert.NoError(t, err) + assert.Empty(t, ops) + }) + + t.Run("rejects an invalid relation", func(t *testing.T) { + _, err := diffPlatformUsers([]PlatformUserSpec{{Type: "user", Ref: "a@x.com", Relation: "owner"}}, nil) + assert.Error(t, err) + }) + + t.Run("rejects an invalid type", func(t *testing.T) { + _, err := diffPlatformUsers([]PlatformUserSpec{{Type: "group", Ref: "a@x.com", Relation: admin}}, nil) + assert.Error(t, err) + }) + + t.Run("rejects an empty ref", func(t *testing.T) { + _, err := diffPlatformUsers([]PlatformUserSpec{{Type: "user", Ref: "", Relation: admin}}, nil) + assert.Error(t, err) + }) + + t.Run("rejects the bootstrap service account (the server manages it)", func(t *testing.T) { + _, err := diffPlatformUsers( + []PlatformUserSpec{{Type: "serviceuser", Ref: schema.BootstrapServiceUserID, Relation: admin}}, nil) + assert.Error(t, err) + }) +} + +// TestSpecRefMatching covers the accepted `ref` forms: a user may be referenced by +// id or by email (case-insensitive); a service user only by id. +func TestSpecRefMatching(t *testing.T) { + admin := schema.AdminRelationName + userP := principal("user", "user-uuid-1", "alice@x.com") + suP := principal("serviceuser", "su-uuid-1", "") // service users carry no email + + t.Run("user matches by id", func(t *testing.T) { + assert.True(t, specMatchesPrincipal(PlatformUserSpec{Type: "user", Ref: "user-uuid-1", Relation: admin}, userP)) + }) + t.Run("user matches by email (case-insensitive)", func(t *testing.T) { + assert.True(t, specMatchesPrincipal(PlatformUserSpec{Type: "user", Ref: "Alice@X.com", Relation: admin}, userP)) + }) + t.Run("service user matches by id", func(t *testing.T) { + assert.True(t, specMatchesPrincipal(PlatformUserSpec{Type: "serviceuser", Ref: "su-uuid-1", Relation: admin}, suP)) + }) + t.Run("service user does NOT match by email (email is user-only)", func(t *testing.T) { + suWithEmail := principal("serviceuser", "su-uuid-1", "svc@x.com") + assert.False(t, specMatchesPrincipal(PlatformUserSpec{Type: "serviceuser", Ref: "svc@x.com", Relation: admin}, suWithEmail)) + }) + t.Run("no match when the type differs", func(t *testing.T) { + assert.False(t, specMatchesPrincipal(PlatformUserSpec{Type: "serviceuser", Ref: "user-uuid-1", Relation: admin}, userP)) + }) + t.Run("no match on an unrelated ref", func(t *testing.T) { + assert.False(t, specMatchesPrincipal(PlatformUserSpec{Type: "user", Ref: "bob@x.com", Relation: admin}, userP)) + }) + + // end-to-end through the diff: a user referenced by id and a service user by id + // both make no change when already in the desired relation. + t.Run("diff makes no change: user by id + serviceuser by id already correct", func(t *testing.T) { + ops, err := diffPlatformUsers( + []PlatformUserSpec{ + {Type: "user", Ref: "user-uuid-1", Relation: admin}, + {Type: "serviceuser", Ref: "su-uuid-1", Relation: admin}, + }, + []platformPrincipal{ + principal("user", "user-uuid-1", "alice@x.com", admin), + principal("serviceuser", "su-uuid-1", "", admin), + }, + ) + assert.NoError(t, err) + assert.Empty(t, ops) + }) +} diff --git a/internal/reconcile/reconcile.go b/internal/reconcile/reconcile.go new file mode 100644 index 000000000..0ebae019a --- /dev/null +++ b/internal/reconcile/reconcile.go @@ -0,0 +1,77 @@ +// Package reconcile makes Frontier platform resources match a desired-state +// file, through the admin API. It is a small framework: each resource kind +// implements Reconciler and registers under its kind, so new kinds (roles, +// plans, products, traits, ...) plug in without changing the command or the +// file format. PlatformUser is the first kind. +package reconcile + +import ( + "bytes" + "context" + "fmt" + "io" + "strings" + + "gopkg.in/yaml.v3" +) + +// Reconciler makes a single resource kind match its desired-state spec. +type Reconciler interface { + Kind() string + Reconcile(ctx context.Context, spec []byte, dryRun bool) (Report, error) +} + +// Report summarises what a reconcile did, or would do when dryRun. +type Report struct { + Kind string + DryRun bool + Planned []string // human-readable operations (the plan) + Applied int // number actually applied (0 when dryRun) +} + +// document is one YAML document in a desired-state file: a kind + its spec. +type document struct { + Kind string `yaml:"kind"` + Spec yaml.Node `yaml:"spec"` +} + +// Run parses a (possibly multi-document) desired-state file and sends each +// document to the reconciler registered for its kind. Documents apply in file +// order. The first error stops the run and returns the reports gathered so far. +func Run(ctx context.Context, registry map[string]Reconciler, data []byte, dryRun bool) ([]Report, error) { + dec := yaml.NewDecoder(bytes.NewReader(data)) + var reports []Report + for { + var doc document + err := dec.Decode(&doc) + if err == io.EOF { + break + } + if err != nil { + return reports, fmt.Errorf("parse desired-state: %w", err) + } + if doc.Kind == "" { + continue // skip empty documents + } + rec, ok := registry[doc.Kind] + if !ok { + return reports, fmt.Errorf("no reconciler registered for kind %q", doc.Kind) + } + specBytes, err := yaml.Marshal(&doc.Spec) + if err != nil { + return reports, fmt.Errorf("marshal spec for kind %q: %w", doc.Kind, err) + } + // A missing or null spec marshals to "null"/"" — almost always a typo (e.g. + // `spce:`). Don't treat that as an empty list that would remove every user; + // an empty list you meant to write is `spec: []`. + if s := strings.TrimSpace(string(specBytes)); s == "" || s == "null" { + return reports, fmt.Errorf("document kind %q is missing its spec", doc.Kind) + } + rep, err := rec.Reconcile(ctx, specBytes, dryRun) + if err != nil { + return reports, fmt.Errorf("reconcile %s: %w", doc.Kind, err) + } + reports = append(reports, rep) + } + return reports, nil +} diff --git a/internal/reconcile/reconcile_test.go b/internal/reconcile/reconcile_test.go new file mode 100644 index 000000000..1d6802ba8 --- /dev/null +++ b/internal/reconcile/reconcile_test.go @@ -0,0 +1,36 @@ +package reconcile + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +type fakeReconciler struct{ called int } + +func (f *fakeReconciler) Kind() string { return KindPlatformUser } +func (f *fakeReconciler) Reconcile(_ context.Context, _ []byte, _ bool) (Report, error) { + f.called++ + return Report{Kind: KindPlatformUser}, nil +} + +func TestRun_SpecHandling(t *testing.T) { + t.Run("rejects a document missing its spec (not an empty list)", func(t *testing.T) { + rec := &fakeReconciler{} + reg := map[string]Reconciler{KindPlatformUser: rec} + + _, err := Run(context.Background(), reg, []byte("kind: PlatformUser\n"), false) + assert.Error(t, err) + assert.Zero(t, rec.called) // never dispatched + }) + + t.Run("dispatches a document that has a spec", func(t *testing.T) { + rec := &fakeReconciler{} + reg := map[string]Reconciler{KindPlatformUser: rec} + + _, err := Run(context.Background(), reg, []byte("kind: PlatformUser\nspec: []\n"), false) + assert.NoError(t, err) + assert.Equal(t, 1, rec.called) + }) +} diff --git a/internal/store/postgres/serviceuser_repository.go b/internal/store/postgres/serviceuser_repository.go index 11b501091..8a5c4e8ae 100644 --- a/internal/store/postgres/serviceuser_repository.go +++ b/internal/store/postgres/serviceuser_repository.go @@ -25,7 +25,9 @@ type ServiceUserRepository struct { type serviceUserWithContext struct { ServiceUser - OrgName string `db:"org_name"` + // OrgName is nullable: a platform-level service user (e.g. the config bootstrap + // superuser) has no real organization row, so the org_name subquery is NULL. + OrgName sql.NullString `db:"org_name"` } func NewServiceUserRepository(dbc *db.Client) *ServiceUserRepository { @@ -130,7 +132,7 @@ func (s ServiceUserRepository) Create(ctx context.Context, serviceUser serviceus AuditResource{ ID: result.OrgID, Type: auditrecord.OrganizationType, - Name: result.OrgName, + Name: result.OrgName.String, }, &AuditTarget{ ID: result.ID, @@ -307,7 +309,7 @@ func (s ServiceUserRepository) Delete(ctx context.Context, id string) error { AuditResource{ ID: result.OrgID, Type: auditrecord.OrganizationType, - Name: result.OrgName, + Name: result.OrgName.String, }, &AuditTarget{ ID: result.ID, diff --git a/test/e2e/testbench/helper.go b/test/e2e/testbench/helper.go index ae98f5ddb..1d6e279c7 100644 --- a/test/e2e/testbench/helper.go +++ b/test/e2e/testbench/helper.go @@ -3,15 +3,18 @@ package testbench import ( "context" _ "embed" + "encoding/base64" "encoding/json" "errors" "fmt" "net" "net/http" "strings" + "time" "connectrpc.com/connect" "github.com/raystack/frontier/core/authenticate/strategy" + "github.com/raystack/frontier/internal/bootstrap/schema" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" "github.com/raystack/frontier/proto/v1beta1/frontierv1beta1connect" ) @@ -30,8 +33,42 @@ var ( const ( OrgAdminEmail = "admin1-group1-org1@raystack.org" TestOTP = "123456" + + // Bootstrap superuser service-account credentials, seeded via App.Admin.Bootstrap. + // Used to grant the test admin platform superuser, mirroring the production GitOps flow. + // client_id is a credential UUID (serviceuser_credentials.id is a uuid column). + BootstrapClientID = "11111111-2222-3333-4444-555555555555" + BootstrapClientSecret = "e2e-bootstrap-secret" ) +// PromoteBootstrapAdmin authenticates as the config-bootstrapped superuser service +// account (HTTP Basic client credentials) and grants the given user the platform +// admin (superuser) relation — the same path GitOps uses in production. +func PromoteBootstrapAdmin(ctx context.Context, ad frontierv1beta1connect.AdminServiceClient, email string) error { + basic := base64.StdEncoding.EncodeToString([]byte(BootstrapClientID + ":" + BootstrapClientSecret)) + authCtx := ContextWithHeaders(ctx, map[string]string{"Authorization": "Basic " + basic}) + + // Retry while the server is still coming up (CodeUnavailable). When multiple + // suites run in one process, a prior suite's Close() SIGINTs the process, so the + // next testbench's server can take a moment to accept connections. + var lastErr error + for i := 0; i < 60; i++ { + _, err := ad.AddPlatformUser(authCtx, connect.NewRequest(&frontierv1beta1.AddPlatformUserRequest{ + UserId: email, + Relation: schema.AdminRelationName, + })) + if err == nil { + return nil + } + lastErr = err + if connect.CodeOf(err) != connect.CodeUnavailable { + return fmt.Errorf("promote bootstrap admin: %w", err) + } + time.Sleep(250 * time.Millisecond) + } + return fmt.Errorf("promote bootstrap admin (server unavailable after retries): %w", lastErr) +} + // headersKey is the context key for storing headers to be sent with ConnectRPC requests. type headersKey struct{} diff --git a/test/e2e/testbench/testbench.go b/test/e2e/testbench/testbench.go index 9f4e33446..fb5561d20 100644 --- a/test/e2e/testbench/testbench.go +++ b/test/e2e/testbench/testbench.go @@ -1,6 +1,7 @@ package testbench import ( + "context" "errors" "fmt" "net" @@ -17,6 +18,7 @@ import ( "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" "github.com/raystack/frontier/config" + "github.com/raystack/frontier/internal/bootstrap" "github.com/raystack/frontier/internal/store/spicedb" "github.com/raystack/frontier/pkg/db" ) @@ -94,7 +96,10 @@ func Init(appConfig *config.Frontier) (*TestBench, error) { PreSharedKey: preSharedKey, Consistency: spicedb.ConsistencyLevelBestEffort.String(), } - appConfig.App.Admin.Users = []string{OrgAdminEmail} + appConfig.App.Admin.Bootstrap = bootstrap.SuperUserBootstrapConfig{ + ClientID: BootstrapClientID, + ClientSecret: BootstrapClientSecret, + } appConfig.App.Webhook.EncryptionKey = "kmm4ECoWU21K2ZoyTcYLd6w7DfhoUoap" appConfig.Billing.StripeWebhookSecrets = []string{"whsec_test_secret"} appConfig.App.Authentication.Token.Claims.AddOrgIDsClaim = true @@ -134,6 +139,13 @@ func Init(appConfig *config.Frontier) (*TestBench, error) { // let frontier start time.Sleep(time.Second * 2) + + // seed the platform superuser the production way: the config-bootstrapped service + // account grants the test admin superuser via the platform API (no boot-time human flow). + if err := PromoteBootstrapAdmin(context.Background(), te.AdminClient, OrgAdminEmail); err != nil { + // containers + server are already up; tear them down so we don't leak. + return nil, errors.Join(err, te.Close()) + } return te, nil } From 5cbf5b2baadd2093e18d060e82559b10548fc331 Mon Sep 17 00:00:00 2001 From: Rohil Surana Date: Wed, 1 Jul 2026 23:30:44 +0530 Subject: [PATCH 06/10] fix(reconcile): return the failing document's partial report so the caller can see what applied --- internal/reconcile/reconcile.go | 4 +++- internal/reconcile/reconcile_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/internal/reconcile/reconcile.go b/internal/reconcile/reconcile.go index 0ebae019a..9189aed1e 100644 --- a/internal/reconcile/reconcile.go +++ b/internal/reconcile/reconcile.go @@ -69,7 +69,9 @@ func Run(ctx context.Context, registry map[string]Reconciler, data []byte, dryRu } rep, err := rec.Reconcile(ctx, specBytes, dryRun) if err != nil { - return reports, fmt.Errorf("reconcile %s: %w", doc.Kind, err) + // Keep this document's report. On a partial apply, rep still holds what + // was applied before the failure, so the caller can show it. + return append(reports, rep), fmt.Errorf("reconcile %s: %w", doc.Kind, err) } reports = append(reports, rep) } diff --git a/internal/reconcile/reconcile_test.go b/internal/reconcile/reconcile_test.go index 1d6802ba8..b258bcabe 100644 --- a/internal/reconcile/reconcile_test.go +++ b/internal/reconcile/reconcile_test.go @@ -2,6 +2,7 @@ package reconcile import ( "context" + "errors" "testing" "github.com/stretchr/testify/assert" @@ -15,6 +16,16 @@ func (f *fakeReconciler) Reconcile(_ context.Context, _ []byte, _ bool) (Report, return Report{Kind: KindPlatformUser}, nil } +// partialReconciler fails after applying some operations, like a real apply that +// dies part-way through. +type partialReconciler struct{} + +func (partialReconciler) Kind() string { return KindPlatformUser } +func (partialReconciler) Reconcile(_ context.Context, _ []byte, _ bool) (Report, error) { + return Report{Kind: KindPlatformUser, Applied: 2, Planned: []string{"add a", "add b", "add c"}}, + errors.New("apply failed on the third op") +} + func TestRun_SpecHandling(t *testing.T) { t.Run("rejects a document missing its spec (not an empty list)", func(t *testing.T) { rec := &fakeReconciler{} @@ -34,3 +45,17 @@ func TestRun_SpecHandling(t *testing.T) { assert.Equal(t, 1, rec.called) }) } + +func TestRun_ReturnsPartialReportOnError(t *testing.T) { + reg := map[string]Reconciler{KindPlatformUser: partialReconciler{}} + + reports, err := Run(context.Background(), reg, []byte("kind: PlatformUser\nspec: []\n"), false) + + // The run stops on the error, but the failing document's report is still + // returned so the caller can see what was applied before the failure. + assert.Error(t, err) + if assert.Len(t, reports, 1) { + assert.Equal(t, 2, reports[0].Applied) + assert.Len(t, reports[0].Planned, 3) + } +} From 67c74db81350fdd27d3b7b43959467d751f72083 Mon Sep 17 00:00:00 2001 From: Rohil Surana Date: Wed, 1 Jul 2026 23:44:52 +0530 Subject: [PATCH 07/10] fix(bootstrap): reuse the fixed-id SA row when recovering a missing credential so boot self-heals --- internal/bootstrap/bootstrapuser.go | 77 ++++++++++++++++-------- internal/bootstrap/bootstrapuser_test.go | 33 ++++++++++ 2 files changed, 86 insertions(+), 24 deletions(-) diff --git a/internal/bootstrap/bootstrapuser.go b/internal/bootstrap/bootstrapuser.go index 5a34823d5..36567a087 100644 --- a/internal/bootstrap/bootstrapuser.go +++ b/internal/bootstrap/bootstrapuser.go @@ -40,14 +40,17 @@ func (c SuperUserBootstrapConfig) title() string { return defaultBootstrapTitle } -// ServiceUserCreator creates a bare service-user row. The bootstrap superuser -// deliberately skips org membership (a platform superuser needs none), so this -// is the repository's Create, not serviceuser.Service.Create. +// ServiceUserCreator creates and looks up a bare service-user row. The bootstrap +// superuser deliberately skips org membership (a platform superuser needs none), +// so this is the repository's Create/GetByID, not serviceuser.Service. type ServiceUserCreator interface { + // GetByID looks up a service user by id and returns serviceuser.ErrNotExist + // when it does not exist. Used to reuse the fixed-id bootstrap row instead of + // creating a duplicate. + GetByID(ctx context.Context, id string) (serviceuser.ServiceUser, error) Create(ctx context.Context, su serviceuser.ServiceUser) (serviceuser.ServiceUser, error) - // Delete removes a service user by id. Used to undo a just-created bootstrap - // service user when creating its credential fails, so repeated boot failures - // don't leave behind service-user rows with no credential. + // Delete removes a service user by id. Used to roll back a bootstrap row that + // this boot just created, when the credential write then fails. Delete(ctx context.Context, id string) error } @@ -126,42 +129,68 @@ func createBootstrapSuperUser( creds ServiceUserCredentialStore, promoter SuperUserPromoter, ) error { - su, err := users.Create(ctx, serviceuser.ServiceUser{ - ID: schema.BootstrapServiceUserID, // fixed id (see schema.BootstrapServiceUserID) - OrgID: schema.PlatformOrgID.String(), - Title: cfg.title(), - }) + suID, created, err := ensureBootstrapServiceUser(ctx, cfg, users) if err != nil { - return fmt.Errorf("bootstrap superuser: create service user: %w", err) + return err } hash, err := bcrypt.GenerateFromPassword([]byte(cfg.ClientSecret), bootstrapBcryptCost) if err != nil { - return cleanupOrphanBootstrapSU(ctx, logger, users, su.ID, + return rollbackFreshBootstrapSU(ctx, logger, users, suID, created, fmt.Errorf("bootstrap superuser: hash secret: %w", err)) } + // creds.Create runs only because ensureBootstrapSuperUser already saw the + // credential (id == clientID) is missing, so we never add a second credential + // for the bootstrap SA. if _, err := creds.Create(ctx, serviceuser.Credential{ ID: clientID, - ServiceUserID: su.ID, + ServiceUserID: suID, Type: serviceuser.ClientSecretCredentialType, SecretHash: string(hash), Title: cfg.title(), }); err != nil { - return cleanupOrphanBootstrapSU(ctx, logger, users, su.ID, + return rollbackFreshBootstrapSU(ctx, logger, users, suID, created, fmt.Errorf("bootstrap superuser: create credential: %w", err)) } logger.InfoContext(ctx, "created bootstrap superuser service account", - "client_id", clientID, "serviceuser_id", su.ID) - return promoteBootstrapSuperUser(ctx, promoter, su.ID) + "client_id", clientID, "serviceuser_id", suID) + return promoteBootstrapSuperUser(ctx, promoter, suID) +} + +// ensureBootstrapServiceUser returns the fixed-id bootstrap service-user row, +// creating it only when it does not already exist. Reusing an existing row keeps +// recovery idempotent: if an earlier boot created the row but failed before +// writing the credential (or a rotation deleted the credential), we must not try +// to create a second row with the same id. The bool reports whether this call +// created the row. +func ensureBootstrapServiceUser(ctx context.Context, cfg SuperUserBootstrapConfig, users ServiceUserCreator) (string, bool, error) { + id := schema.BootstrapServiceUserID + if _, err := users.GetByID(ctx, id); err == nil { + return id, false, nil + } else if !errors.Is(err, serviceuser.ErrNotExist) { + return "", false, fmt.Errorf("bootstrap superuser: get service user: %w", err) + } + su, err := users.Create(ctx, serviceuser.ServiceUser{ + ID: id, // fixed id (see schema.BootstrapServiceUserID) + OrgID: schema.PlatformOrgID.String(), + Title: cfg.title(), + }) + if err != nil { + return "", false, fmt.Errorf("bootstrap superuser: create service user: %w", err) + } + return su.ID, true, nil } -// cleanupOrphanBootstrapSU tries to delete a service user that was just created -// when a later step (hashing, or creating the credential) fails. Without it the -// row would be left behind with no credential and no superuser grant, and the next -// boot, still finding no credential, would create another one. The original error -// is always returned; a failed cleanup is only logged (the next boot retries anyway). -func cleanupOrphanBootstrapSU(ctx context.Context, logger *slog.Logger, users ServiceUserCreator, suID string, cause error) error { +// rollbackFreshBootstrapSU deletes the service-user row when a later step fails, +// but only if THIS boot created it. If we reused an existing row (an earlier boot +// created it, or a rotation left it in place), we leave it — the next boot finds +// it by its fixed id and retries the missing credential. Recovery is idempotent, +// so a failed rollback is not fatal. The original error is always returned. +func rollbackFreshBootstrapSU(ctx context.Context, logger *slog.Logger, users ServiceUserCreator, suID string, created bool, cause error) error { + if !created { + return cause + } if err := users.Delete(ctx, suID); err != nil { - logger.WarnContext(ctx, "failed to roll back orphan bootstrap service user", + logger.WarnContext(ctx, "failed to roll back freshly created bootstrap service user", "serviceuser_id", suID, "err", err.Error()) } return cause diff --git a/internal/bootstrap/bootstrapuser_test.go b/internal/bootstrap/bootstrapuser_test.go index 114f75835..4970341ba 100644 --- a/internal/bootstrap/bootstrapuser_test.go +++ b/internal/bootstrap/bootstrapuser_test.go @@ -17,6 +17,11 @@ import ( type mockSUCreator struct{ mock.Mock } +func (m *mockSUCreator) GetByID(ctx context.Context, id string) (serviceuser.ServiceUser, error) { + args := m.Called(ctx, id) + return args.Get(0).(serviceuser.ServiceUser), args.Error(1) +} + func (m *mockSUCreator) Create(ctx context.Context, su serviceuser.ServiceUser) (serviceuser.ServiceUser, error) { args := m.Called(ctx, su) return args.Get(0).(serviceuser.ServiceUser), args.Error(1) @@ -100,6 +105,7 @@ func TestEnsureBootstrapSuperUser(t *testing.T) { cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{}, serviceuser.ErrCredNotExist) + users.On("GetByID", mock.Anything, schema.BootstrapServiceUserID).Return(serviceuser.ServiceUser{}, serviceuser.ErrNotExist) users.On("Create", mock.Anything, mock.MatchedBy(func(su serviceuser.ServiceUser) bool { return su.ID == schema.BootstrapServiceUserID && su.OrgID == schema.PlatformOrgID.String() && su.Title == defaultBootstrapTitle @@ -169,6 +175,7 @@ func TestEnsureBootstrapSuperUser(t *testing.T) { cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{}, serviceuser.ErrCredNotExist) + users.On("GetByID", mock.Anything, schema.BootstrapServiceUserID).Return(serviceuser.ServiceUser{}, serviceuser.ErrNotExist) users.On("Create", mock.Anything, mock.Anything).Return(serviceuser.ServiceUser{ID: "su-id"}, nil) creds.On("Create", mock.Anything, mock.Anything).Return(serviceuser.Credential{}, errors.New("db down")) users.On("Delete", mock.Anything, "su-id").Return(nil) @@ -179,6 +186,32 @@ func TestEnsureBootstrapSuperUser(t *testing.T) { prom.AssertNotCalled(t, "Sudo", mock.Anything, mock.Anything, mock.Anything) }) + t.Run("recovers by reusing the existing service user when its credential is missing", func(t *testing.T) { + users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} + + // The row already exists (an earlier boot created it, or a rotation deleted + // the credential and left the row) but the credential is gone. + users.On("GetByID", mock.Anything, schema.BootstrapServiceUserID). + Return(serviceuser.ServiceUser{ID: schema.BootstrapServiceUserID}, nil) + creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{}, serviceuser.ErrCredNotExist) + var created serviceuser.Credential + creds.On("Create", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { created = args.Get(1).(serviceuser.Credential) }). + Return(serviceuser.Credential{ID: clientID}, nil) + prom.On("Sudo", mock.Anything, schema.BootstrapServiceUserID, schema.AdminRelationName).Return(nil) + + assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, cfg, users, creds, prom)) + creds.AssertExpectations(t) + prom.AssertExpectations(t) + // Reused the row: no second service user, and no rollback of the existing one. + users.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) + users.AssertNotCalled(t, "Delete", mock.Anything, mock.Anything) + // Exactly one credential, linked to the fixed row. + assert.Equal(t, clientID, created.ID) + assert.Equal(t, schema.BootstrapServiceUserID, created.ServiceUserID) + }) + t.Run("aborts on a non-not-found credential lookup error", func(t *testing.T) { users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} From 10a12c581c854806a8f0e77ba67412e3d881786e Mon Sep 17 00:00:00 2001 From: Rohil Surana Date: Thu, 2 Jul 2026 10:43:17 +0530 Subject: [PATCH 08/10] refactor(platform): trim inline comments, keep concise docs at the top of functions --- core/serviceuser/service.go | 15 ++-- core/user/service.go | 15 ++-- internal/api/v1beta1connect/platform.go | 29 +++---- internal/api/v1beta1connect/serviceuser.go | 13 ++-- internal/bootstrap/bootstrapuser.go | 76 +++++++------------ internal/bootstrap/schema/schema.go | 8 +- internal/bootstrap/service.go | 6 +- internal/reconcile/platformuser.go | 40 ++++------ internal/reconcile/platformuser_reconciler.go | 13 +--- internal/reconcile/reconcile.go | 29 ++++--- 10 files changed, 89 insertions(+), 155 deletions(-) diff --git a/core/serviceuser/service.go b/core/serviceuser/service.go index 1e56d131d..4260ce524 100644 --- a/core/serviceuser/service.go +++ b/core/serviceuser/service.go @@ -468,18 +468,15 @@ func (s Service) Sudo(ctx context.Context, id string, relationName string) error return fmt.Errorf("invalid relation name, possible options are: %s, %s", schema.MemberRelationName, schema.AdminRelationName) } - // Check the exact relation, not the permission it grants. Running this again - // is safe. Both admin and member grant `check`, so checking the permission - // would skip creating the member relation for someone who is already an admin. - // That breaks a downgrade (Sudo member, then UnSudo admin), because UnSudo - // works at the relation level. Keep the two the same. + // Act on the exact relation, not the permission it grants: admin and member both + // grant `check`, so a permission check would skip adding member to an existing + // admin and break the admin->member downgrade. Safe to run again. if ok, err := s.IsSudo(ctx, currentUser.ID, relationName); err != nil { return err } else if ok { return nil } - // mark su _, err = s.relationService.Create(ctx, relation.Relation{ Object: relation.Object{ ID: schema.PlatformID, @@ -551,10 +548,8 @@ func (s Service) UnSudo(ctx context.Context, id, relationName string) error { return s.recordPlatformAuditRecord(ctx, currentUser, event, relationName) } -// recordPlatformAuditRecord writes an audit record when a platform relation -// (admin or member) is granted or removed on a service user. Actor is left empty, -// so the repository fills it in from the context (the caller, or the system actor -// at boot). +// recordPlatformAuditRecord logs a platform admin/member grant or revoke on a service +// user. Actor is left empty; the repository fills it from context (caller or boot). func (s Service) recordPlatformAuditRecord(ctx context.Context, su ServiceUser, event pkgAuditRecord.Event, relationName string) error { _, err := s.auditRecordRepository.Create(ctx, models.AuditRecord{ Event: event, diff --git a/core/user/service.go b/core/user/service.go index be497b50b..3a97d7adf 100644 --- a/core/user/service.go +++ b/core/user/service.go @@ -188,18 +188,15 @@ func (s Service) Sudo(ctx context.Context, id string, relationName string) error return fmt.Errorf("invalid relation name, possible options are: %s, %s", schema.MemberRelationName, schema.AdminRelationName) } - // Check the exact relation, not the permission it grants. Running this again - // is safe. Both admin and member grant `check`, so checking the permission - // would skip creating the member relation for someone who is already an admin. - // That breaks a downgrade (Sudo member, then UnSudo admin), because UnSudo - // works at the relation level. Keep the two the same. + // Act on the exact relation, not the permission it grants: admin and member both + // grant `check`, so a permission check would skip adding member to an existing + // admin and break the admin->member downgrade. Safe to run again. if ok, err := s.IsSudo(ctx, currentUser.ID, relationName); err != nil { return err } else if ok { return nil } - // mark su _, err = s.relationService.Create(ctx, relation.Relation{ Object: relation.Object{ ID: schema.PlatformID, @@ -271,10 +268,8 @@ func (s Service) UnSudo(ctx context.Context, id, relationName string) error { return s.recordPlatformAuditRecord(ctx, currentUser, event, relationName) } -// recordPlatformAuditRecord writes an audit record when a platform relation -// (admin or member) is granted or removed. Actor is left empty, so the repository -// fills it in from the context: the user or account making the request, or the -// system actor (uuid.Nil) for changes at boot or from config. +// recordPlatformAuditRecord logs a platform admin/member grant or revoke. Actor is +// left empty; the repository fills it from context (caller, or system actor at boot). func (s Service) recordPlatformAuditRecord(ctx context.Context, u User, event pkgAuditRecord.Event, relationName string) error { _, err := s.auditRecordRepository.Create(ctx, models.AuditRecord{ Event: event, diff --git a/internal/api/v1beta1connect/platform.go b/internal/api/v1beta1connect/platform.go index bdba5e90f..710c5e110 100644 --- a/internal/api/v1beta1connect/platform.go +++ b/internal/api/v1beta1connect/platform.go @@ -37,10 +37,8 @@ func (h *ConnectHandler) AddPlatformUser(ctx context.Context, req *connect.Reque } func (h *ConnectHandler) RemovePlatformUser(ctx context.Context, req *connect.Request[frontierv1beta1.RemovePlatformUserRequest]) (*connect.Response[frontierv1beta1.RemovePlatformUserResponse], error) { - // By default, remove the user or service account from the platform completely - // (both admin and member). If a relation is set, remove just that one — for - // example, to turn an admin into a member. UnSudo does nothing if they don't - // hold that relation. + // No relation set → remove both admin and member; a relation scopes removal to + // just that one (e.g. demote admin to member). platformRelations := []string{schema.AdminRelationName, schema.MemberRelationName} if rel := req.Msg.GetRelation(); rel != "" { if !schema.IsPlatformRelation(rel) { @@ -57,9 +55,8 @@ func (h *ConnectHandler) RemovePlatformUser(ctx context.Context, req *connect.Re } } } else if req.Msg.GetServiceuserId() != "" { - // The server manages the bootstrap SA (it seeds it at boot), so it can't be - // removed through this API. Return the generic permission error and log why. - // The response must not reveal that this id is the protected SA. + // The bootstrap SA can't be removed via the API. Return the generic permission + // error (must not reveal it's the protected SA) and log the real reason. if req.Msg.GetServiceuserId() == schema.BootstrapServiceUserID { slog.WarnContext(ctx, "refused removal of the bootstrap superuser service account", "service_user_id", req.Msg.GetServiceuserId()) return nil, connect.NewError(connect.CodePermissionDenied, ErrUnauthorized) @@ -86,14 +83,11 @@ func (h *ConnectHandler) ListPlatformUsers(ctx context.Context, req *connect.Req return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("ListPlatformUsers.ListRelations: %w", err)) } - // A user or service account can hold both admin and member. Collect every - // relation for each subject, with duplicates removed, so callers see all of - // them and not just the last one listed. The reconciler needs this to know - // exactly which relations to remove. + // A subject can hold both admin and member; collect all relations per subject so + // the reconciler knows exactly which to remove, not just the last one listed. userRelations := platformRelationsBySubject(relations, schema.UserPrincipal) serviceUserRelations := platformRelationsBySubject(relations, schema.ServiceUserPrincipal) - // fetch users userIDs := sortedSubjectIDs(userRelations) userPBs := make([]*frontierv1beta1.User, 0, len(userIDs)) if len(userIDs) > 0 { @@ -114,7 +108,6 @@ func (h *ConnectHandler) ListPlatformUsers(ctx context.Context, req *connect.Req } } - // fetch service users serviceUserIDs := sortedSubjectIDs(serviceUserRelations) serviceUserPBs := make([]*frontierv1beta1.ServiceUser, 0, len(serviceUserIDs)) if len(serviceUserIDs) > 0 { @@ -141,9 +134,8 @@ func (h *ConnectHandler) ListPlatformUsers(ctx context.Context, req *connect.Req }), nil } -// platformRelationsBySubject groups the platform relation names (admin/member) -// by subject id for the given namespace. Duplicates are removed and the result -// is sorted so the output is stable. +// platformRelationsBySubject groups platform relation names by subject id for the +// namespace, deduped and sorted for stable output. func platformRelationsBySubject(relations []relation.Relation, namespace string) map[string][]string { sets := map[string]map[string]struct{}{} for _, r := range relations { @@ -177,9 +169,8 @@ func sortedSubjectIDs(m map[string][]string) []string { return ids } -// stampPlatformRelations records a subject's platform relations in its metadata. -// "relations" holds all of them (used by the reconciler); "relation" keeps the -// first one for backward compatibility. +// stampPlatformRelations writes a subject's relations to metadata: "relations" (all, +// used by the reconciler) and "relation" (the first, for backward compatibility). func stampPlatformRelations(md map[string]any, rels []string) { if len(rels) == 0 { return diff --git a/internal/api/v1beta1connect/serviceuser.go b/internal/api/v1beta1connect/serviceuser.go index 3731239a5..5a1d64cbe 100644 --- a/internal/api/v1beta1connect/serviceuser.go +++ b/internal/api/v1beta1connect/serviceuser.go @@ -169,14 +169,11 @@ func (h *ConnectHandler) CreateServiceUser(ctx context.Context, request *connect }), nil } -// errBootstrapSAImmutable returns a PermissionDenied error when id is the bootstrap -// SA (its fixed id). That account is managed only through app.admin.bootstrap at -// boot. The API must never delete it or create credentials, keys, or tokens for it. -// Doing so would leave a superuser way in that config can't rotate away. Not even -// platform superusers are allowed. -// -// The response is the generic permission error, so it doesn't reveal that this id is -// the protected SA. The specific reason is only logged. +// errBootstrapSAImmutable returns PermissionDenied when id is the bootstrap SA. That +// account is managed only via app.admin.bootstrap at boot; the API must never delete +// it or mint credentials/keys/tokens for it (that would be an un-rotatable backdoor), +// not even for superusers. The error is generic so it can't reveal the protected id; +// the real reason is only logged. func errBootstrapSAImmutable(ctx context.Context, id string) error { if id == schema.BootstrapServiceUserID { slog.WarnContext(ctx, "refused API mutation of the bootstrap superuser service account", "service_user_id", id) diff --git a/internal/bootstrap/bootstrapuser.go b/internal/bootstrap/bootstrapuser.go index 36567a087..77d32a2de 100644 --- a/internal/bootstrap/bootstrapuser.go +++ b/internal/bootstrap/bootstrapuser.go @@ -14,19 +14,15 @@ import ( "github.com/raystack/frontier/internal/bootstrap/schema" ) -// bootstrapBcryptCost matches serviceuser.CreateSecret's cost for client secrets. -// It is a var (not a const) so unit tests can lower it to bcrypt.MinCost — cost 14 -// is deliberately expensive and, run under `-race -count 2`, blows the test timeout. +// bootstrapBcryptCost is a var (not const) so tests can drop it to bcrypt.MinCost; +// cost 14 under `-race -count 2` blows the test timeout. var bootstrapBcryptCost = 14 const defaultBootstrapTitle = "GitOps Bootstrap Superuser" -// SuperUserBootstrapConfig configures a superuser service account seeded from -// config. It gives a username/password-style credential (client_id + client_secret) -// so automation (like the GitOps reconcile flow) always has a superuser to log in as -// at boot. This means you don't need an existing superuser to create the first one. -// You provide the secret, and it is rotated when it changes. Leave client_id and -// client_secret empty to turn this off. +// SuperUserBootstrapConfig configures a superuser service account seeded from config, +// so automation (like GitOps reconcile) always has a superuser to log in as at boot — +// no existing superuser needed to create the first one. Leave both fields empty to disable. type SuperUserBootstrapConfig struct { ClientID string `yaml:"client_id" mapstructure:"client_id"` ClientSecret string `yaml:"client_secret" mapstructure:"client_secret"` @@ -40,17 +36,14 @@ func (c SuperUserBootstrapConfig) title() string { return defaultBootstrapTitle } -// ServiceUserCreator creates and looks up a bare service-user row. The bootstrap -// superuser deliberately skips org membership (a platform superuser needs none), -// so this is the repository's Create/GetByID, not serviceuser.Service. +// ServiceUserCreator creates and looks up a bare service-user row (no org membership; +// a platform superuser needs none), backed by the repository, not serviceuser.Service. type ServiceUserCreator interface { - // GetByID looks up a service user by id and returns serviceuser.ErrNotExist - // when it does not exist. Used to reuse the fixed-id bootstrap row instead of - // creating a duplicate. + // GetByID returns serviceuser.ErrNotExist when absent, letting us reuse the + // fixed-id bootstrap row instead of creating a duplicate. GetByID(ctx context.Context, id string) (serviceuser.ServiceUser, error) Create(ctx context.Context, su serviceuser.ServiceUser) (serviceuser.ServiceUser, error) - // Delete removes a service user by id. Used to roll back a bootstrap row that - // this boot just created, when the credential write then fails. + // Delete rolls back a row this boot just created when the credential write fails. Delete(ctx context.Context, id string) error } @@ -66,19 +59,15 @@ type SuperUserPromoter interface { Sudo(ctx context.Context, id, relationName string) error } -// EnsureBootstrapSuperUser makes sure the configured superuser service account -// exists and is a platform superuser. It is safe to run on every boot. It does -// nothing when the account is not configured. +// EnsureBootstrapSuperUser makes sure the configured superuser service account exists +// and is a platform superuser. Safe to run on every boot; a no-op when unconfigured. func (s Service) EnsureBootstrapSuperUser(ctx context.Context) error { return ensureBootstrapSuperUser(ctx, s.logger, s.adminConfig.Bootstrap, s.suCreator, s.suCredStore, s.suPromoter) } -// ensureBootstrapSuperUser holds the testable core. The account lives in the -// platform/nil org (serviceusers.org_id is nullable, no FK) and is created -// without org membership. It decides what to do based on whether the credential -// id (client_id) already exists: -// - not there: create the service user + client-secret credential + promote it; -// - there: rotate the secret if it changed, then make sure the superuser relation is set. +// ensureBootstrapSuperUser is the testable core, keyed on the credential id (client_id): +// if the credential is absent it creates the service user + credential + promotes; +// if present it rotates the secret when it changed, then re-asserts the superuser relation. func ensureBootstrapSuperUser( ctx context.Context, logger *slog.Logger, @@ -88,17 +77,14 @@ func ensureBootstrapSuperUser( promoter SuperUserPromoter, ) error { clientID := strings.TrimSpace(cfg.ClientID) - // Only skip when the config is fully unset. A half-configured bootstrap (only - // one of client_id/client_secret) is a mistake that would quietly skip seeding - // the superuser — fail early instead. + // Skip only when fully unset; a half-configured bootstrap is a mistake, so fail fast. switch { case clientID == "" && cfg.ClientSecret == "": return nil case clientID == "" || cfg.ClientSecret == "": return errors.New("bootstrap superuser: client_id and client_secret must be set together") } - // client_id is the service-user credential's id, which is a UUID column; a - // non-UUID would otherwise fail with an opaque SQL error deep in the lookup. + // client_id is a UUID credential-id column; validate up front for a clear error. if _, err := uuid.Parse(clientID); err != nil { return fmt.Errorf("bootstrap superuser: client_id must be a valid uuid: %w", err) } @@ -138,9 +124,8 @@ func createBootstrapSuperUser( return rollbackFreshBootstrapSU(ctx, logger, users, suID, created, fmt.Errorf("bootstrap superuser: hash secret: %w", err)) } - // creds.Create runs only because ensureBootstrapSuperUser already saw the - // credential (id == clientID) is missing, so we never add a second credential - // for the bootstrap SA. + // Reached only because the caller saw the credential is missing, so this never + // adds a second credential for the bootstrap SA. if _, err := creds.Create(ctx, serviceuser.Credential{ ID: clientID, ServiceUserID: suID, @@ -156,12 +141,9 @@ func createBootstrapSuperUser( return promoteBootstrapSuperUser(ctx, promoter, suID) } -// ensureBootstrapServiceUser returns the fixed-id bootstrap service-user row, -// creating it only when it does not already exist. Reusing an existing row keeps -// recovery idempotent: if an earlier boot created the row but failed before -// writing the credential (or a rotation deleted the credential), we must not try -// to create a second row with the same id. The bool reports whether this call -// created the row. +// ensureBootstrapServiceUser returns the fixed-id bootstrap row, creating it only if +// absent so recovery is idempotent (never a duplicate row). The bool reports whether +// this call created it. func ensureBootstrapServiceUser(ctx context.Context, cfg SuperUserBootstrapConfig, users ServiceUserCreator) (string, bool, error) { id := schema.BootstrapServiceUserID if _, err := users.GetByID(ctx, id); err == nil { @@ -170,7 +152,7 @@ func ensureBootstrapServiceUser(ctx context.Context, cfg SuperUserBootstrapConfi return "", false, fmt.Errorf("bootstrap superuser: get service user: %w", err) } su, err := users.Create(ctx, serviceuser.ServiceUser{ - ID: id, // fixed id (see schema.BootstrapServiceUserID) + ID: id, OrgID: schema.PlatformOrgID.String(), Title: cfg.title(), }) @@ -180,11 +162,9 @@ func ensureBootstrapServiceUser(ctx context.Context, cfg SuperUserBootstrapConfi return su.ID, true, nil } -// rollbackFreshBootstrapSU deletes the service-user row when a later step fails, -// but only if THIS boot created it. If we reused an existing row (an earlier boot -// created it, or a rotation left it in place), we leave it — the next boot finds -// it by its fixed id and retries the missing credential. Recovery is idempotent, -// so a failed rollback is not fatal. The original error is always returned. +// rollbackFreshBootstrapSU deletes the row on failure only if this boot created it; +// a reused (possibly promoted) row is left for the next boot to retry. Rollback is +// best-effort — the original error is always returned. func rollbackFreshBootstrapSU(ctx context.Context, logger *slog.Logger, users ServiceUserCreator, suID string, created bool, cause error) error { if !created { return cause @@ -196,8 +176,8 @@ func rollbackFreshBootstrapSU(ctx context.Context, logger *slog.Logger, users Se return cause } -// rotateBootstrapSecret replaces the stored secret. The credential repo has no -// update, so delete + recreate with the same id (a brief gap at boot is fine). +// rotateBootstrapSecret replaces the stored secret. The credential repo has no update, +// so it deletes and recreates with the same id (a brief gap at boot is fine). func rotateBootstrapSecret( ctx context.Context, cfg SuperUserBootstrapConfig, diff --git a/internal/bootstrap/schema/schema.go b/internal/bootstrap/schema/schema.go index 0b671c377..a63c0a149 100644 --- a/internal/bootstrap/schema/schema.go +++ b/internal/bootstrap/schema/schema.go @@ -17,11 +17,9 @@ const ( // Global IDs PlatformID = "platform" - // BootstrapServiceUserID is the fixed service-user id of the bootstrap superuser - // SA from config (app.admin.bootstrap). It is deliberately not uuid.Nil: uuid.Nil - // is reserved for the audit "system" actor, so a real principal must not use it. - // Because this id is fixed, the platform API can refuse to remove the SA and the - // reconciler can skip it, with no lookup needed. + // BootstrapServiceUserID is the fixed service-user id of the config bootstrap SA + // (app.admin.bootstrap). Deliberately not uuid.Nil (reserved for the audit "system" + // actor). Being fixed lets the API refuse to remove it and the reconciler skip it. BootstrapServiceUserID = "00000000-0000-0000-0000-000000000001" // namespace diff --git a/internal/bootstrap/service.go b/internal/bootstrap/service.go index 12407a65d..6c4866bf8 100644 --- a/internal/bootstrap/service.go +++ b/internal/bootstrap/service.go @@ -80,10 +80,8 @@ type ServiceUserBackfiller interface { // AdminConfig is platform administration configuration type AdminConfig struct { - // Bootstrap seeds a superuser service account from config (client_id/secret) so - // automation (e.g. GitOps) always has a superuser to log in as. This means you - // don't need an existing superuser to create the first one. Everything else about - // platform users is managed separately, through the GitOps reconcile flow. + // Bootstrap seeds a superuser service account from config so automation (e.g. + // GitOps) always has a superuser to log in as. See SuperUserBootstrapConfig. Bootstrap SuperUserBootstrapConfig `yaml:"bootstrap" mapstructure:"bootstrap"` } diff --git a/internal/reconcile/platformuser.go b/internal/reconcile/platformuser.go index f1b6cebaa..237954beb 100644 --- a/internal/reconcile/platformuser.go +++ b/internal/reconcile/platformuser.go @@ -16,17 +16,16 @@ const ( ) // PlatformUserSpec is one desired platform-user entry from the YAML spec. -// Relation is the platform relation to grant — admin (-> superuser) or member -// (-> check). It is deliberately "relation", not "role": "role" is a distinct -// RBAC concept in Frontier, whereas these are SpiceDB relation names. +// Relation is "admin" or "member" — a SpiceDB relation, not an RBAC "role" +// (a separate concept in Frontier), hence the field name. type PlatformUserSpec struct { Type string `yaml:"type"` // "user" | "serviceuser" Ref string `yaml:"ref"` // email or uuid for a user; id for a service user Relation string `yaml:"relation"` // "admin" | "member" } -// platformPrincipal is the current state of one platform principal (from ListPlatformUsers), -// with the set of platform relations it currently holds. +// platformPrincipal is the current state of one platform principal, as returned +// by ListPlatformUsers. type platformPrincipal struct { Type string ID string @@ -34,7 +33,6 @@ type platformPrincipal struct { Relations map[string]struct{} } -// opAction is an apply operation kind. type opAction string const ( @@ -42,9 +40,8 @@ const ( opRemove opAction = "remove" ) -// Op is a single planned change to one (principal, relation). "add" grants the -// relation; "remove" takes away just that one relation. Ref holds the desired -// entry's ref for an add (email or id), and the current principal's id for a remove. +// Op is a single planned change to one (principal, relation). Ref is the desired +// entry's ref for an add (email or id) and the current principal's id for a remove. type Op struct { Action opAction Type string @@ -60,7 +57,7 @@ func (o Op) String() string { } // principalIDs maps the op's ref onto the request id fields: a user ref fills -// user_id, anything else fills serviceuser_id. Exactly one is non-empty. +// user_id, otherwise serviceuser_id. Exactly one is non-empty. func (o Op) principalIDs() (userID, serviceUserID string) { if o.Type == principalTypeUser { return o.Ref, "" @@ -82,17 +79,15 @@ func validateSpec(s PlatformUserSpec) error { if strings.TrimSpace(s.Ref) == "" { return fmt.Errorf("empty ref") } - // The server manages the bootstrap SA (it seeds it at boot and blocks removal), - // so it must not be reconciled. Reject it on the desired side too, not just skip - // it on the current side. + // The bootstrap SA is server-managed; reject it here too, not just skip it on the current side. if s.Type == principalTypeServiceUser && strings.TrimSpace(s.Ref) == schema.BootstrapServiceUserID { return fmt.Errorf("ref %q is the bootstrap service account, which the server manages and cannot be reconciled", s.Ref) } return nil } -// specMatchesPrincipal reports whether a desired spec refers to a current principal. -// Users match by id or email; service users by id. +// specMatchesPrincipal reports whether a desired spec refers to a current +// principal: users match by id or email, service users by id. func specMatchesPrincipal(s PlatformUserSpec, p platformPrincipal) bool { if s.Type != p.Type { return false @@ -103,14 +98,10 @@ func specMatchesPrincipal(s PlatformUserSpec, p platformPrincipal) bool { return p.Type == principalTypeUser && s.Ref != "" && strings.EqualFold(s.Ref, p.Email) } -// platformRelationOrder gives operations a stable, fixed order. var platformRelationOrder = []string{schema.AdminRelationName, schema.MemberRelationName} -// diffPlatformUsers works out the changes needed to make the current platform -// principals match the desired spec, one (principal, relation) at a time. A current -// relation that is no longer wanted is removed. A wanted relation that is missing is -// added. A desired entry that matches no current principal is new, so it is added. -// The output order is fixed. +// diffPlatformUsers returns the ops that make the current platform principals +// match the desired spec, per (principal, relation). Order is stable. func diffPlatformUsers(desired []PlatformUserSpec, current []platformPrincipal) ([]Op, error) { for _, s := range desired { if err := validateSpec(s); err != nil { @@ -118,10 +109,8 @@ func diffPlatformUsers(desired []PlatformUserSpec, current []platformPrincipal) } } - // Collect adds and removes separately so adds come first (see the return). - // Apply runs in order, so doing adds before removes means a relation change - // (e.g. admin -> member) never leaves someone with no platform access if a - // later op fails. + // Emit adds before removes so a relation change (admin -> member) never drops + // all access if a later op fails. var adds, removes []Op matched := make([]bool, len(desired)) @@ -145,7 +134,6 @@ func diffPlatformUsers(desired []PlatformUserSpec, current []platformPrincipal) } } - // add desired entries that match no current platform principal seenNewRef := map[string]struct{}{} for i, s := range desired { if matched[i] { diff --git a/internal/reconcile/platformuser_reconciler.go b/internal/reconcile/platformuser_reconciler.go index 8d592ec02..df9f043da 100644 --- a/internal/reconcile/platformuser_reconciler.go +++ b/internal/reconcile/platformuser_reconciler.go @@ -79,9 +79,7 @@ func (r *PlatformUserReconciler) fetchCurrent(ctx context.Context) ([]platformPr }) } for _, su := range resp.Msg.GetServiceusers() { - // The bootstrap SA (created from config at boot) has a fixed id. The server - // manages it and blocks removal, so never reconcile it — that way we never - // try to remove it. + // Never reconcile the bootstrap SA — it is server-managed and removal-blocked. if su.GetId() == schema.BootstrapServiceUserID { continue } @@ -95,7 +93,6 @@ func (r *PlatformUserReconciler) fetchCurrent(ctx context.Context) ([]platformPr } func (r *PlatformUserReconciler) apply(ctx context.Context, op Op) error { - // exactly one of the two ids is set, by principal type. userID, serviceUserID := op.principalIDs() switch op.Action { case opAdd: @@ -104,7 +101,7 @@ func (r *PlatformUserReconciler) apply(ctx context.Context, op Op) error { }, r.header)) return err case opRemove: - // remove only op.Relation, not both (proton #489). + // relation-selective removal (proton #489) _, err := r.client.RemovePlatformUser(ctx, authReq(&frontierv1beta1.RemovePlatformUserRequest{ UserId: userID, ServiceuserId: serviceUserID, Relation: op.Relation, }, r.header)) @@ -114,10 +111,8 @@ func (r *PlatformUserReconciler) apply(ctx context.Context, op Op) error { } } -// relationsFromMetadata reads the platform relations that ListPlatformUsers records -// in each principal's metadata. It uses the full "relations" list when present (so -// someone holding both admin and member is handled correctly), and falls back to the -// single "relation" field for backward compatibility. +// relationsFromMetadata reads the platform relations ListPlatformUsers records in a +// principal's metadata: the "relations" list, falling back to "relation". func relationsFromMetadata(md *structpb.Struct) map[string]struct{} { rels := map[string]struct{}{} if md == nil { diff --git a/internal/reconcile/reconcile.go b/internal/reconcile/reconcile.go index 9189aed1e..d5a92bed4 100644 --- a/internal/reconcile/reconcile.go +++ b/internal/reconcile/reconcile.go @@ -1,8 +1,7 @@ -// Package reconcile makes Frontier platform resources match a desired-state -// file, through the admin API. It is a small framework: each resource kind -// implements Reconciler and registers under its kind, so new kinds (roles, -// plans, products, traits, ...) plug in without changing the command or the -// file format. PlatformUser is the first kind. +// Package reconcile makes Frontier platform resources match a desired-state file +// through the admin API. Each resource kind implements Reconciler and registers +// under its kind, so new kinds plug in without changing the command or file +// format. PlatformUser is the first kind. package reconcile import ( @@ -25,19 +24,18 @@ type Reconciler interface { type Report struct { Kind string DryRun bool - Planned []string // human-readable operations (the plan) + Planned []string // the plan, human-readable Applied int // number actually applied (0 when dryRun) } -// document is one YAML document in a desired-state file: a kind + its spec. type document struct { Kind string `yaml:"kind"` Spec yaml.Node `yaml:"spec"` } -// Run parses a (possibly multi-document) desired-state file and sends each -// document to the reconciler registered for its kind. Documents apply in file -// order. The first error stops the run and returns the reports gathered so far. +// Run dispatches each document in a (possibly multi-document) desired-state file +// to the reconciler for its kind, in file order. The first error stops the run +// and returns the reports gathered so far. func Run(ctx context.Context, registry map[string]Reconciler, data []byte, dryRun bool) ([]Report, error) { dec := yaml.NewDecoder(bytes.NewReader(data)) var reports []Report @@ -51,7 +49,7 @@ func Run(ctx context.Context, registry map[string]Reconciler, data []byte, dryRu return reports, fmt.Errorf("parse desired-state: %w", err) } if doc.Kind == "" { - continue // skip empty documents + continue } rec, ok := registry[doc.Kind] if !ok { @@ -61,16 +59,15 @@ func Run(ctx context.Context, registry map[string]Reconciler, data []byte, dryRu if err != nil { return reports, fmt.Errorf("marshal spec for kind %q: %w", doc.Kind, err) } - // A missing or null spec marshals to "null"/"" — almost always a typo (e.g. - // `spce:`). Don't treat that as an empty list that would remove every user; - // an empty list you meant to write is `spec: []`. + // A missing spec marshals to "null"/"" (usually a typo). Reject it rather + // than treat it as an empty list that removes everyone; write `spec: []` to + // mean that on purpose. if s := strings.TrimSpace(string(specBytes)); s == "" || s == "null" { return reports, fmt.Errorf("document kind %q is missing its spec", doc.Kind) } rep, err := rec.Reconcile(ctx, specBytes, dryRun) if err != nil { - // Keep this document's report. On a partial apply, rep still holds what - // was applied before the failure, so the caller can show it. + // Return rep too: on a partial apply it shows what was applied. return append(reports, rep), fmt.Errorf("reconcile %s: %w", doc.Kind, err) } reports = append(reports, rep) From c854f40a2de89102161811bb0c620fbabdceaeb6 Mon Sep 17 00:00:00 2001 From: Rohil Surana Date: Thu, 2 Jul 2026 11:08:23 +0530 Subject: [PATCH 09/10] refactor(bootstrap): rename ServiceUserCreator param from users to serviceUsers to avoid confusion with human users --- internal/bootstrap/bootstrapuser.go | 22 ++++---- internal/bootstrap/bootstrapuser_test.go | 68 ++++++++++++------------ 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/internal/bootstrap/bootstrapuser.go b/internal/bootstrap/bootstrapuser.go index 77d32a2de..1b1dea290 100644 --- a/internal/bootstrap/bootstrapuser.go +++ b/internal/bootstrap/bootstrapuser.go @@ -72,7 +72,7 @@ func ensureBootstrapSuperUser( ctx context.Context, logger *slog.Logger, cfg SuperUserBootstrapConfig, - users ServiceUserCreator, + serviceUsers ServiceUserCreator, creds ServiceUserCredentialStore, promoter SuperUserPromoter, ) error { @@ -92,7 +92,7 @@ func ensureBootstrapSuperUser( cred, err := creds.Get(ctx, clientID) switch { case errors.Is(err, serviceuser.ErrCredNotExist): - return createBootstrapSuperUser(ctx, logger, cfg, clientID, users, creds, promoter) + return createBootstrapSuperUser(ctx, logger, cfg, clientID, serviceUsers, creds, promoter) case err != nil: return fmt.Errorf("bootstrap superuser: get credential %q: %w", clientID, err) } @@ -111,17 +111,17 @@ func createBootstrapSuperUser( logger *slog.Logger, cfg SuperUserBootstrapConfig, clientID string, - users ServiceUserCreator, + serviceUsers ServiceUserCreator, creds ServiceUserCredentialStore, promoter SuperUserPromoter, ) error { - suID, created, err := ensureBootstrapServiceUser(ctx, cfg, users) + suID, created, err := ensureBootstrapServiceUser(ctx, cfg, serviceUsers) if err != nil { return err } hash, err := bcrypt.GenerateFromPassword([]byte(cfg.ClientSecret), bootstrapBcryptCost) if err != nil { - return rollbackFreshBootstrapSU(ctx, logger, users, suID, created, + return rollbackFreshBootstrapSU(ctx, logger, serviceUsers, suID, created, fmt.Errorf("bootstrap superuser: hash secret: %w", err)) } // Reached only because the caller saw the credential is missing, so this never @@ -133,7 +133,7 @@ func createBootstrapSuperUser( SecretHash: string(hash), Title: cfg.title(), }); err != nil { - return rollbackFreshBootstrapSU(ctx, logger, users, suID, created, + return rollbackFreshBootstrapSU(ctx, logger, serviceUsers, suID, created, fmt.Errorf("bootstrap superuser: create credential: %w", err)) } logger.InfoContext(ctx, "created bootstrap superuser service account", @@ -144,14 +144,14 @@ func createBootstrapSuperUser( // ensureBootstrapServiceUser returns the fixed-id bootstrap row, creating it only if // absent so recovery is idempotent (never a duplicate row). The bool reports whether // this call created it. -func ensureBootstrapServiceUser(ctx context.Context, cfg SuperUserBootstrapConfig, users ServiceUserCreator) (string, bool, error) { +func ensureBootstrapServiceUser(ctx context.Context, cfg SuperUserBootstrapConfig, serviceUsers ServiceUserCreator) (string, bool, error) { id := schema.BootstrapServiceUserID - if _, err := users.GetByID(ctx, id); err == nil { + if _, err := serviceUsers.GetByID(ctx, id); err == nil { return id, false, nil } else if !errors.Is(err, serviceuser.ErrNotExist) { return "", false, fmt.Errorf("bootstrap superuser: get service user: %w", err) } - su, err := users.Create(ctx, serviceuser.ServiceUser{ + su, err := serviceUsers.Create(ctx, serviceuser.ServiceUser{ ID: id, OrgID: schema.PlatformOrgID.String(), Title: cfg.title(), @@ -165,11 +165,11 @@ func ensureBootstrapServiceUser(ctx context.Context, cfg SuperUserBootstrapConfi // rollbackFreshBootstrapSU deletes the row on failure only if this boot created it; // a reused (possibly promoted) row is left for the next boot to retry. Rollback is // best-effort — the original error is always returned. -func rollbackFreshBootstrapSU(ctx context.Context, logger *slog.Logger, users ServiceUserCreator, suID string, created bool, cause error) error { +func rollbackFreshBootstrapSU(ctx context.Context, logger *slog.Logger, serviceUsers ServiceUserCreator, suID string, created bool, cause error) error { if !created { return cause } - if err := users.Delete(ctx, suID); err != nil { + if err := serviceUsers.Delete(ctx, suID); err != nil { logger.WarnContext(ctx, "failed to roll back freshly created bootstrap service user", "serviceuser_id", suID, "err", err.Error()) } diff --git a/internal/bootstrap/bootstrapuser_test.go b/internal/bootstrap/bootstrapuser_test.go index 4970341ba..2cdb04375 100644 --- a/internal/bootstrap/bootstrapuser_test.go +++ b/internal/bootstrap/bootstrapuser_test.go @@ -75,38 +75,38 @@ func TestEnsureBootstrapSuperUser(t *testing.T) { const clientID = "f47ac10b-58cc-4372-a567-0e02b2c3d479" t.Run("does nothing when not configured", func(t *testing.T) { - users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + serviceUsers, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) - assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, SuperUserBootstrapConfig{}, users, creds, prom)) + assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, SuperUserBootstrapConfig{}, serviceUsers, creds, prom)) creds.AssertNotCalled(t, "Get", mock.Anything, mock.Anything) - users.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) + serviceUsers.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) prom.AssertNotCalled(t, "Sudo", mock.Anything, mock.Anything, mock.Anything) }) t.Run("rejects a half-configured bootstrap (one of id/secret missing)", func(t *testing.T) { - users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + serviceUsers, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) - assert.Error(t, ensureBootstrapSuperUser(ctx, logger, SuperUserBootstrapConfig{ClientID: clientID}, users, creds, prom)) - assert.Error(t, ensureBootstrapSuperUser(ctx, logger, SuperUserBootstrapConfig{ClientSecret: "s3cret"}, users, creds, prom)) + assert.Error(t, ensureBootstrapSuperUser(ctx, logger, SuperUserBootstrapConfig{ClientID: clientID}, serviceUsers, creds, prom)) + assert.Error(t, ensureBootstrapSuperUser(ctx, logger, SuperUserBootstrapConfig{ClientSecret: "s3cret"}, serviceUsers, creds, prom)) creds.AssertNotCalled(t, "Get", mock.Anything, mock.Anything) - users.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) + serviceUsers.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) }) t.Run("rejects a non-uuid client_id", func(t *testing.T) { - users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + serviceUsers, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) cfg := SuperUserBootstrapConfig{ClientID: "not-a-uuid", ClientSecret: "s3cret"} - assert.Error(t, ensureBootstrapSuperUser(ctx, logger, cfg, users, creds, prom)) + assert.Error(t, ensureBootstrapSuperUser(ctx, logger, cfg, serviceUsers, creds, prom)) creds.AssertNotCalled(t, "Get", mock.Anything, mock.Anything) }) t.Run("creates service user + credential + promotes when absent", func(t *testing.T) { - users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + serviceUsers, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{}, serviceuser.ErrCredNotExist) - users.On("GetByID", mock.Anything, schema.BootstrapServiceUserID).Return(serviceuser.ServiceUser{}, serviceuser.ErrNotExist) - users.On("Create", mock.Anything, mock.MatchedBy(func(su serviceuser.ServiceUser) bool { + serviceUsers.On("GetByID", mock.Anything, schema.BootstrapServiceUserID).Return(serviceuser.ServiceUser{}, serviceuser.ErrNotExist) + serviceUsers.On("Create", mock.Anything, mock.MatchedBy(func(su serviceuser.ServiceUser) bool { return su.ID == schema.BootstrapServiceUserID && su.OrgID == schema.PlatformOrgID.String() && su.Title == defaultBootstrapTitle })).Return(serviceuser.ServiceUser{ID: "su-id"}, nil) @@ -119,8 +119,8 @@ func TestEnsureBootstrapSuperUser(t *testing.T) { Return(serviceuser.Credential{ID: clientID}, nil) prom.On("Sudo", mock.Anything, "su-id", schema.AdminRelationName).Return(nil) - assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, cfg, users, creds, prom)) - users.AssertExpectations(t) + assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, cfg, serviceUsers, creds, prom)) + serviceUsers.AssertExpectations(t) creds.AssertExpectations(t) prom.AssertExpectations(t) @@ -131,7 +131,7 @@ func TestEnsureBootstrapSuperUser(t *testing.T) { }) t.Run("ensures superuser without rotating when the secret matches", func(t *testing.T) { - users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + serviceUsers, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{ @@ -139,15 +139,15 @@ func TestEnsureBootstrapSuperUser(t *testing.T) { }, nil) prom.On("Sudo", mock.Anything, "su-id", schema.AdminRelationName).Return(nil) - assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, cfg, users, creds, prom)) + assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, cfg, serviceUsers, creds, prom)) prom.AssertExpectations(t) creds.AssertNotCalled(t, "Delete", mock.Anything, mock.Anything) creds.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) - users.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) + serviceUsers.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) }) t.Run("rotates the secret when it changed, then ensures superuser", func(t *testing.T) { - users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + serviceUsers, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "new-secret"} creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{ @@ -160,10 +160,10 @@ func TestEnsureBootstrapSuperUser(t *testing.T) { Return(serviceuser.Credential{ID: clientID}, nil) prom.On("Sudo", mock.Anything, "su-id", schema.AdminRelationName).Return(nil) - assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, cfg, users, creds, prom)) + assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, cfg, serviceUsers, creds, prom)) creds.AssertExpectations(t) prom.AssertExpectations(t) - users.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) + serviceUsers.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) assert.Equal(t, clientID, rotated.ID) assert.Equal(t, "su-id", rotated.ServiceUserID) @@ -171,28 +171,28 @@ func TestEnsureBootstrapSuperUser(t *testing.T) { }) t.Run("rolls back the service user when credential creation fails", func(t *testing.T) { - users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + serviceUsers, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{}, serviceuser.ErrCredNotExist) - users.On("GetByID", mock.Anything, schema.BootstrapServiceUserID).Return(serviceuser.ServiceUser{}, serviceuser.ErrNotExist) - users.On("Create", mock.Anything, mock.Anything).Return(serviceuser.ServiceUser{ID: "su-id"}, nil) + serviceUsers.On("GetByID", mock.Anything, schema.BootstrapServiceUserID).Return(serviceuser.ServiceUser{}, serviceuser.ErrNotExist) + serviceUsers.On("Create", mock.Anything, mock.Anything).Return(serviceuser.ServiceUser{ID: "su-id"}, nil) creds.On("Create", mock.Anything, mock.Anything).Return(serviceuser.Credential{}, errors.New("db down")) - users.On("Delete", mock.Anything, "su-id").Return(nil) + serviceUsers.On("Delete", mock.Anything, "su-id").Return(nil) - assert.Error(t, ensureBootstrapSuperUser(ctx, logger, cfg, users, creds, prom)) - users.AssertExpectations(t) // Create + compensating Delete both invoked + assert.Error(t, ensureBootstrapSuperUser(ctx, logger, cfg, serviceUsers, creds, prom)) + serviceUsers.AssertExpectations(t) // Create + compensating Delete both invoked creds.AssertExpectations(t) prom.AssertNotCalled(t, "Sudo", mock.Anything, mock.Anything, mock.Anything) }) t.Run("recovers by reusing the existing service user when its credential is missing", func(t *testing.T) { - users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + serviceUsers, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} // The row already exists (an earlier boot created it, or a rotation deleted // the credential and left the row) but the credential is gone. - users.On("GetByID", mock.Anything, schema.BootstrapServiceUserID). + serviceUsers.On("GetByID", mock.Anything, schema.BootstrapServiceUserID). Return(serviceuser.ServiceUser{ID: schema.BootstrapServiceUserID}, nil) creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{}, serviceuser.ErrCredNotExist) var created serviceuser.Credential @@ -201,25 +201,25 @@ func TestEnsureBootstrapSuperUser(t *testing.T) { Return(serviceuser.Credential{ID: clientID}, nil) prom.On("Sudo", mock.Anything, schema.BootstrapServiceUserID, schema.AdminRelationName).Return(nil) - assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, cfg, users, creds, prom)) + assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, cfg, serviceUsers, creds, prom)) creds.AssertExpectations(t) prom.AssertExpectations(t) // Reused the row: no second service user, and no rollback of the existing one. - users.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) - users.AssertNotCalled(t, "Delete", mock.Anything, mock.Anything) + serviceUsers.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) + serviceUsers.AssertNotCalled(t, "Delete", mock.Anything, mock.Anything) // Exactly one credential, linked to the fixed row. assert.Equal(t, clientID, created.ID) assert.Equal(t, schema.BootstrapServiceUserID, created.ServiceUserID) }) t.Run("aborts on a non-not-found credential lookup error", func(t *testing.T) { - users, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + serviceUsers, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{}, errors.New("db down")) - assert.Error(t, ensureBootstrapSuperUser(ctx, logger, cfg, users, creds, prom)) - users.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) + assert.Error(t, ensureBootstrapSuperUser(ctx, logger, cfg, serviceUsers, creds, prom)) + serviceUsers.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) prom.AssertNotCalled(t, "Sudo", mock.Anything, mock.Anything, mock.Anything) }) } From 37f085fd33404b33d1d97b789a4fc4c6c940ea19 Mon Sep 17 00:00:00 2001 From: Rohil Surana Date: Thu, 2 Jul 2026 11:37:14 +0530 Subject: [PATCH 10/10] fix(bootstrap): refuse boot when client_id points at another service user's credential --- internal/bootstrap/bootstrapuser.go | 6 ++++++ internal/bootstrap/bootstrapuser_test.go | 26 +++++++++++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/internal/bootstrap/bootstrapuser.go b/internal/bootstrap/bootstrapuser.go index 1b1dea290..01c09c22a 100644 --- a/internal/bootstrap/bootstrapuser.go +++ b/internal/bootstrap/bootstrapuser.go @@ -96,6 +96,12 @@ func ensureBootstrapSuperUser( case err != nil: return fmt.Errorf("bootstrap superuser: get credential %q: %w", clientID, err) } + // A misconfigured client_id pointing at another SA's credential would silently + // rotate that credential and promote the wrong principal — fail loudly instead. + if cred.ServiceUserID != schema.BootstrapServiceUserID { + return fmt.Errorf("bootstrap superuser: credential %q belongs to service user %q, want bootstrap service user %q", + clientID, cred.ServiceUserID, schema.BootstrapServiceUserID) + } if bcrypt.CompareHashAndPassword([]byte(cred.SecretHash), []byte(cfg.ClientSecret)) != nil { if err := rotateBootstrapSecret(ctx, cfg, clientID, cred, creds); err != nil { diff --git a/internal/bootstrap/bootstrapuser_test.go b/internal/bootstrap/bootstrapuser_test.go index 2cdb04375..4bc14bab7 100644 --- a/internal/bootstrap/bootstrapuser_test.go +++ b/internal/bootstrap/bootstrapuser_test.go @@ -135,9 +135,9 @@ func TestEnsureBootstrapSuperUser(t *testing.T) { cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{ - ID: clientID, ServiceUserID: "su-id", SecretHash: bcryptHash(t, "s3cret"), + ID: clientID, ServiceUserID: schema.BootstrapServiceUserID, SecretHash: bcryptHash(t, "s3cret"), }, nil) - prom.On("Sudo", mock.Anything, "su-id", schema.AdminRelationName).Return(nil) + prom.On("Sudo", mock.Anything, schema.BootstrapServiceUserID, schema.AdminRelationName).Return(nil) assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, cfg, serviceUsers, creds, prom)) prom.AssertExpectations(t) @@ -151,14 +151,14 @@ func TestEnsureBootstrapSuperUser(t *testing.T) { cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "new-secret"} creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{ - ID: clientID, ServiceUserID: "su-id", SecretHash: bcryptHash(t, "old-secret"), Title: "t", + ID: clientID, ServiceUserID: schema.BootstrapServiceUserID, SecretHash: bcryptHash(t, "old-secret"), Title: "t", }, nil) creds.On("Delete", mock.Anything, clientID).Return(nil) var rotated serviceuser.Credential creds.On("Create", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { rotated = args.Get(1).(serviceuser.Credential) }). Return(serviceuser.Credential{ID: clientID}, nil) - prom.On("Sudo", mock.Anything, "su-id", schema.AdminRelationName).Return(nil) + prom.On("Sudo", mock.Anything, schema.BootstrapServiceUserID, schema.AdminRelationName).Return(nil) assert.NoError(t, ensureBootstrapSuperUser(ctx, logger, cfg, serviceUsers, creds, prom)) creds.AssertExpectations(t) @@ -166,10 +166,26 @@ func TestEnsureBootstrapSuperUser(t *testing.T) { serviceUsers.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) assert.Equal(t, clientID, rotated.ID) - assert.Equal(t, "su-id", rotated.ServiceUserID) + assert.Equal(t, schema.BootstrapServiceUserID, rotated.ServiceUserID) assert.NoError(t, bcrypt.CompareHashAndPassword([]byte(rotated.SecretHash), []byte("new-secret"))) }) + t.Run("refuses when the credential belongs to another service user", func(t *testing.T) { + serviceUsers, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) + cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"} + + // A mistyped client_id pointing at another SA's credential must fail boot, + // not rotate that credential or promote its owner. + creds.On("Get", mock.Anything, clientID).Return(serviceuser.Credential{ + ID: clientID, ServiceUserID: "22222222-2222-2222-2222-222222222222", SecretHash: bcryptHash(t, "other-secret"), + }, nil) + + assert.Error(t, ensureBootstrapSuperUser(ctx, logger, cfg, serviceUsers, creds, prom)) + creds.AssertNotCalled(t, "Delete", mock.Anything, mock.Anything) + creds.AssertNotCalled(t, "Create", mock.Anything, mock.Anything) + prom.AssertNotCalled(t, "Sudo", mock.Anything, mock.Anything, mock.Anything) + }) + t.Run("rolls back the service user when credential creation fails", func(t *testing.T) { serviceUsers, creds, prom := new(mockSUCreator), new(mockCredStore), new(mockSUPromoter) cfg := SuperUserBootstrapConfig{ClientID: clientID, ClientSecret: "s3cret"}