diff --git a/api/authmethods/oidc_auth_method_attributes.gen.go b/api/authmethods/oidc_auth_method_attributes.gen.go index d2bac7fe85..2409ea5f7c 100644 --- a/api/authmethods/oidc_auth_method_attributes.gen.go +++ b/api/authmethods/oidc_auth_method_attributes.gen.go @@ -26,6 +26,7 @@ type OidcAuthMethodAttributes struct { AccountClaimMaps []string `json:"account_claim_maps,omitempty"` DisableDiscoveredConfigValidation bool `json:"disable_discovered_config_validation,omitempty"` DryRun bool `json:"dry_run,omitempty"` + Prompts []string `json:"prompts,omitempty"` } func AttributesMapToOidcAuthMethodAttributes(in map[string]interface{}) (*OidcAuthMethodAttributes, error) { diff --git a/api/authmethods/option.gen.go b/api/authmethods/option.gen.go index dcba776e60..a66082e467 100644 --- a/api/authmethods/option.gen.go +++ b/api/authmethods/option.gen.go @@ -799,6 +799,30 @@ func DefaultName() Option { } } +func WithOidcAuthMethodPrompts(inPrompts []string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["prompts"] = inPrompts + o.postMap["attributes"] = val + } +} + +func DefaultOidcAuthMethodPrompts() Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["prompts"] = nil + o.postMap["attributes"] = val + } +} + func WithOidcAuthMethodSigningAlgorithms(inSigningAlgorithms []string) Option { return func(o *options) { raw, ok := o.postMap["attributes"] diff --git a/internal/auth/oidc/auth_method.go b/internal/auth/oidc/auth_method.go index 416132efa7..5b719eb710 100644 --- a/internal/auth/oidc/auth_method.go +++ b/internal/auth/oidc/auth_method.go @@ -107,6 +107,12 @@ func NewAuthMethod(ctx context.Context, scopeId string, clientId string, clientS a.SigningAlgs = append(a.SigningAlgs, string(alg)) } } + if len(opts.withPrompts) > 0 { + a.Prompts = make([]string, 0, len(opts.withPrompts)) + for _, prompts := range opts.withPrompts { + a.Prompts = append(a.Prompts, string(prompts)) + } + } if len(opts.withAccountClaimMap) > 0 { a.AccountClaimMaps = make([]string, 0, len(opts.withAccountClaimMap)) for k, v := range opts.withAccountClaimMap { @@ -282,6 +288,7 @@ type convertedValues struct { Certs []any ClaimsScopes []any AccountClaimMaps []any + Prompts []any } // convertValueObjects converts the embedded value objects. It will return an @@ -292,7 +299,7 @@ func (am *AuthMethod) convertValueObjects(ctx context.Context) (*convertedValues return nil, errors.New(ctx, errors.InvalidPublicId, op, "missing public id") } var err error - var addAlgs, addAuds, addCerts, addScopes, addAccountClaimMaps []any + var addAlgs, addAuds, addCerts, addScopes, addAccountClaimMaps, addPrompts []any if addAlgs, err = am.convertSigningAlgs(ctx); err != nil { return nil, errors.Wrap(ctx, err, op) } @@ -308,12 +315,16 @@ func (am *AuthMethod) convertValueObjects(ctx context.Context) (*convertedValues if addAccountClaimMaps, err = am.convertAccountClaimMaps(ctx); err != nil { return nil, errors.Wrap(ctx, err, op) } + if addPrompts, err = am.convertPrompts(ctx); err != nil { + return nil, errors.Wrap(ctx, err, op) + } return &convertedValues{ Algs: addAlgs, Auds: addAuds, Certs: addCerts, ClaimsScopes: addScopes, AccountClaimMaps: addAccountClaimMaps, + Prompts: addPrompts, }, nil } @@ -458,3 +469,22 @@ func ParseAccountClaimMaps(ctx context.Context, m ...string) ([]ClaimMap, error) } return claimMap, nil } + +// convertPrompts converts the embedded prompts from []string +// to []interface{} where each slice element is a *Prompt. It will return an +// error if the AuthMethod's public id is not set. +func (am *AuthMethod) convertPrompts(ctx context.Context) ([]any, error) { + const op = "oidc.(AuthMethod).convertPrompts" + if am.PublicId == "" { + return nil, errors.New(ctx, errors.InvalidPublicId, op, "missing public id") + } + newInterfaces := make([]any, 0, len(am.Prompts)) + for _, a := range am.Prompts { + obj, err := NewPrompt(ctx, am.PublicId, PromptParam(a)) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + newInterfaces = append(newInterfaces, obj) + } + return newInterfaces, nil +} diff --git a/internal/auth/oidc/auth_method_test.go b/internal/auth/oidc/auth_method_test.go index 8730954eca..11ec01b2ad 100644 --- a/internal/auth/oidc/auth_method_test.go +++ b/internal/auth/oidc/auth_method_test.go @@ -591,6 +591,14 @@ func Test_convertValueObjects(t *testing.T) { testAccountClaimMaps = append(testAccountClaimMaps, obj) } + testPrompts := []string{"consent", "select_account"} + testExpectedPrompts := make([]any, 0, len(testPrompts)) + for _, a := range testPrompts { + obj, err := NewPrompt(ctx, testPublicId, PromptParam(a)) + require.NoError(t, err) + testExpectedPrompts = append(testExpectedPrompts, obj) + } + tests := []struct { name string authMethodId string @@ -599,6 +607,7 @@ func Test_convertValueObjects(t *testing.T) { certs []string scopes []string maps []string + prompts []string wantValues *convertedValues wantErrMatch *errors.Template wantErrContains string @@ -611,12 +620,14 @@ func Test_convertValueObjects(t *testing.T) { certs: testCerts, scopes: testScopes, maps: testClaimMaps, + prompts: testPrompts, wantValues: &convertedValues{ Algs: testSigningAlgs, Auds: testAudiences, Certs: testCertificates, ClaimsScopes: testClaimsScopes, AccountClaimMaps: testAccountClaimMaps, + Prompts: testExpectedPrompts, }, }, { @@ -636,6 +647,7 @@ func Test_convertValueObjects(t *testing.T) { Certificates: tt.certs, ClaimsScopes: tt.scopes, AccountClaimMaps: tt.maps, + Prompts: tt.prompts, }, } @@ -693,6 +705,14 @@ func Test_convertValueObjects(t *testing.T) { assert.Equal(want, got) } + convertedPrompts, err := am.convertPrompts(ctx) + if tt.wantErrMatch != nil { + require.Error(err) + assert.Truef(errors.Match(tt.wantErrMatch, err), "wanted err %q and got: %+v", tt.wantErrMatch.Code, err) + } else { + assert.Equal(tt.wantValues.Prompts, convertedPrompts) + } + values, err := am.convertValueObjects(ctx) if tt.wantErrMatch != nil { require.Error(err) diff --git a/internal/auth/oidc/immutable_fields_test.go b/internal/auth/oidc/immutable_fields_test.go index 8c20980503..53225be88e 100644 --- a/internal/auth/oidc/immutable_fields_test.go +++ b/internal/auth/oidc/immutable_fields_test.go @@ -430,3 +430,80 @@ func TestAccount_ImmutableFields(t *testing.T) { }) } } + +func TestPrompt_ImmutableFields(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + rw := db.New(conn) + ctx := context.Background() + databaseWrapper, err := kmsCache.GetWrapper(ctx, org.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + + ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} + + am := TestAuthMethod(t, conn, databaseWrapper, org.PublicId, InactiveState, "alice_rp", "my-dogs-name", + WithApiUrl(TestConvertToUrls(t, "https://api.com")[0]), WithPrompts(SelectAccount)) + + new := AllocPrompt() + require.NoError(t, rw.LookupWhere(ctx, &new, "oidc_method_id = ? and prompt = ?", []any{am.PublicId, SelectAccount})) + + tests := []struct { + name string + update *Prompt + fieldMask []string + }{ + { + name: "oidc_method_id", + update: func() *Prompt { + cp := new.Clone() + cp.OidcMethodId = "p_thisIsNotAValidId" + return cp + }(), + fieldMask: []string{"PublicId"}, + }, + { + name: "create time", + update: func() *Prompt { + cp := new.Clone() + cp.CreateTime = &ts + return cp + }(), + fieldMask: []string{"CreateTime"}, + }, + { + name: "prompt", + update: func() *Prompt { + cp := new.Clone() + cp.PromptParam = string(Consent) + return cp + }(), + fieldMask: []string{"PromptParam"}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + orig := new.Clone() + orig.SetTableName(defaultAuthMethodTableName) + require.NoError(rw.LookupWhere(ctx, &new, "oidc_method_id = ? and prompt = ?", []any{orig.OidcMethodId, orig.PromptParam})) + + require.NoError(err) + + tt.update.SetTableName(defaultAuthMethodTableName) + rowsUpdated, err := rw.Update(context.Background(), tt.update, tt.fieldMask, nil, db.WithSkipVetForWrite(true)) + require.Error(err) + assert.Equal(0, rowsUpdated) + + after := new.Clone() + after.SetTableName(defaultAuthMethodTableName) + require.NoError(rw.LookupWhere(ctx, &new, "oidc_method_id = ? and prompt = ?", []any{after.OidcMethodId, after.PromptParam})) + + assert.True(proto.Equal(orig, after)) + }) + } +} diff --git a/internal/auth/oidc/options.go b/internal/auth/oidc/options.go index b7614e9785..3ab4341aa8 100644 --- a/internal/auth/oidc/options.go +++ b/internal/auth/oidc/options.go @@ -33,6 +33,7 @@ type options struct { withAudClaims []string withSigningAlgs []Alg withClaimsScopes []string + withPrompts []PromptParam withEmail string withFullName string withOrderByCreateTime bool @@ -232,3 +233,10 @@ func WithReader(reader db.Reader) Option { o.withReader = reader } } + +// WithPrompts provides optional prompts +func WithPrompts(prompt ...PromptParam) Option { + return func(o *options) { + o.withPrompts = prompt + } +} diff --git a/internal/auth/oidc/prompt.go b/internal/auth/oidc/prompt.go new file mode 100644 index 0000000000..0b3028691f --- /dev/null +++ b/internal/auth/oidc/prompt.go @@ -0,0 +1,117 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package oidc + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/auth/oidc/store" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/cap/oidc" + "google.golang.org/protobuf/proto" +) + +// Prompt represents OIDC authentication prompt +type PromptParam string + +const ( + // Prompt values defined by OpenID specs. + // See: https://openid.net/specs/openid-connect-basic-1_0.html#RequestParameters + None PromptParam = "none" + Login PromptParam = "login" + Consent PromptParam = "consent" + SelectAccount PromptParam = "select_account" +) + +var supportedPrompts = map[PromptParam]bool{ + None: true, + Login: true, + Consent: true, + SelectAccount: true, +} + +// SupportedPrompt returns true if the provided prompt is supported +// by boundary. +func SupportedPrompt(p PromptParam) bool { + return supportedPrompts[p] +} + +// defaultPromptTableName defines the default table name for a Prompt +const defaultPromptTableName = "auth_oidc_prompt" + +// Prompt defines an prompt supported by an OIDC auth method. +// It is assigned to an OIDC AuthMethod and updates/deletes to that AuthMethod +// are cascaded to its Prompts. Prompts are value objects of an AuthMethod, +// therefore there's no need for oplog metadata, since only the AuthMethod will have +// metadata because it's the root aggregate. +type Prompt struct { + *store.Prompt + tableName string +} + +// NewPrompt creates a new in memory prompt assigned to an OIDC +// AuthMethod. It supports no options. +func NewPrompt(ctx context.Context, authMethodId string, p PromptParam) (*Prompt, error) { + const op = "oidc.NewPrompt" + prompt := &Prompt{ + Prompt: &store.Prompt{ + OidcMethodId: authMethodId, + PromptParam: string(p), + }, + } + if err := prompt.validate(ctx, op); err != nil { + return nil, err // intentionally not wrapped + } + return prompt, nil +} + +// validate the Prompt. On success, it will return nil. +func (s *Prompt) validate(ctx context.Context, caller errors.Op) error { + if s.OidcMethodId == "" { + return errors.New(ctx, errors.InvalidParameter, caller, "missing oidc auth method id") + } + if _, ok := supportedPrompts[PromptParam(s.PromptParam)]; !ok { + return errors.New(ctx, errors.InvalidParameter, caller, fmt.Sprintf("unsupported prompt: %s", s.Prompt)) + } + return nil +} + +func convertToOIDCPrompts(ctx context.Context, p []string) []oidc.Prompt { + prompts := make([]oidc.Prompt, 0, len(p)) + for _, a := range p { + prompt := oidc.Prompt(a) + prompts = append(prompts, prompt) + } + + return prompts +} + +// AllocPrompt makes an empty one in memory +func AllocPrompt() Prompt { + return Prompt{ + Prompt: &store.Prompt{}, + } +} + +// Clone a Prompt +func (s *Prompt) Clone() *Prompt { + cp := proto.Clone(s.Prompt) + return &Prompt{ + Prompt: cp.(*store.Prompt), + } +} + +// TableName returns the table name. +func (s *Prompt) TableName() string { + if s.tableName != "" { + return s.tableName + } + return defaultPromptTableName +} + +// SetTableName sets the table name. +func (s *Prompt) SetTableName(n string) { + s.tableName = n +} diff --git a/internal/auth/oidc/prompt_test.go b/internal/auth/oidc/prompt_test.go new file mode 100644 index 0000000000..de83188012 --- /dev/null +++ b/internal/auth/oidc/prompt_test.go @@ -0,0 +1,330 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package oidc + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +func TestPrompts_Create(t *testing.T) { + t.Parallel() + ctx := context.TODO() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + rw := db.New(conn) + + databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + + testAuthMethod := TestAuthMethod(t, conn, databaseWrapper, org.PublicId, InactiveState, "alice_rp", "my-dogs-name", + WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), WithApiUrl(TestConvertToUrls(t, "https://api.com")[0])) + + type args struct { + authMethodId string + prompt PromptParam + } + tests := []struct { + name string + args args + want *Prompt + wantErr bool + wantIsErr errors.Code + create bool + wantCreateErr bool + wantCreateIsErr errors.Code + }{ + { + name: "valid", + args: args{ + authMethodId: testAuthMethod.PublicId, + prompt: SelectAccount, + }, + create: true, + want: func() *Prompt { + want := AllocPrompt() + want.OidcMethodId = testAuthMethod.PublicId + want.PromptParam = string(SelectAccount) + return &want + }(), + }, + { + name: "dup", // must follow "valid" test. Prompt must be be unique for an OidcMethodId + args: args{ + authMethodId: testAuthMethod.PublicId, + prompt: SelectAccount, + }, + create: true, + want: func() *Prompt { + want := AllocPrompt() + want.OidcMethodId = testAuthMethod.PublicId + want.PromptParam = string(SelectAccount) + return &want + }(), + wantCreateErr: true, + wantCreateIsErr: errors.NotUnique, + }, + { + name: "empty-auth-method", + args: args{ + authMethodId: "", + prompt: Consent, + }, + wantErr: true, + wantIsErr: errors.InvalidParameter, + }, + { + name: "empty-prompt", + args: args{ + authMethodId: testAuthMethod.PublicId, + prompt: "", + }, + wantErr: true, + wantIsErr: errors.InvalidParameter, + }, + { + name: "supported-prompt", + args: args{ + authMethodId: testAuthMethod.PublicId, + prompt: PromptParam("EVE256"), // The unsupported evesdropper 256 curve + }, + wantErr: true, + wantIsErr: errors.InvalidParameter, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, err := NewPrompt(ctx, tt.args.authMethodId, tt.args.prompt) + if tt.wantErr { + require.Error(err) + assert.True(errors.Match(errors.T(tt.wantIsErr), err)) + return + } + require.NoError(err) + assert.Equal(tt.want, got) + if tt.create { + ctx := context.Background() + err = rw.Create(ctx, got) + if tt.wantCreateErr { + assert.Error(err) + assert.True(errors.Match(errors.T(tt.wantCreateIsErr), err)) + return + } else { + assert.NoError(err) + } + found := AllocPrompt() + require.NoError(rw.LookupWhere(ctx, &found, "oidc_method_id = ? and prompt = ?", []any{tt.args.authMethodId, string(tt.args.prompt)})) + assert.Equal(got, &found) + } + }) + } +} + +func TestPrompt_Delete(t *testing.T) { + t.Parallel() + ctx := context.TODO() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + rw := db.New(conn) + + databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + + testAuthMethod := TestAuthMethod( + t, + conn, + databaseWrapper, + org.PublicId, + InactiveState, + "alice_rp", + "my-dogs-name", + WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), + WithApiUrl(TestConvertToUrls(t, "https://api.com")[0]), + WithPrompts(Consent)) + + testResource := func(authMethodId string, prompt PromptParam) *Prompt { + c, err := NewPrompt(ctx, authMethodId, prompt) + require.NoError(t, err) + return c + } + tests := []struct { + name string + Prompt *Prompt + wantRowsDeleted int + overrides func(*Prompt) + wantErr bool + wantErrMsg string + }{ + { + name: "valid", + Prompt: testResource(testAuthMethod.PublicId, SelectAccount), + wantErr: false, + wantRowsDeleted: 1, + }, + { + name: "bad-OidcMethodId", + Prompt: testResource(testAuthMethod.PublicId, Login), + overrides: func(c *Prompt) { c.OidcMethodId = "bad-id" }, + wantErr: false, + wantRowsDeleted: 0, + }, + { + name: "bad-prompt", + Prompt: testResource(testAuthMethod.PublicId, None), + overrides: func(c *Prompt) { c.PromptParam = "bad-prompt" }, + wantErr: false, + wantRowsDeleted: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + cp := tt.Prompt.Clone() + require.NoError(rw.Create(ctx, &cp)) + + if tt.overrides != nil { + tt.overrides(cp) + } + deletedRows, err := rw.Delete(ctx, &cp) + if tt.wantErr { + require.Error(err) + return + } + require.NoError(err) + if tt.wantRowsDeleted == 0 { + assert.Equal(tt.wantRowsDeleted, deletedRows) + return + } + assert.Equal(tt.wantRowsDeleted, deletedRows) + found := AllocPrompt() + err = rw.LookupWhere(ctx, &found, "oidc_method_id = ? and prompt = ?", []any{tt.Prompt.OidcMethodId, tt.Prompt.String()}) + assert.Truef(errors.IsNotFoundError(err), "unexpected error: %s", err.Error()) + }) + } +} + +func TestPrompt_Clone(t *testing.T) { + t.Parallel() + ctx := context.TODO() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + t.Run("valid", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(err) + m := TestAuthMethod(t, conn, databaseWrapper, org.PublicId, InactiveState, "alice_rp", "my-dogs-name", + WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), WithApiUrl(TestConvertToUrls(t, "https://api.com")[0])) + orig, err := NewPrompt(ctx, m.PublicId, Consent) + require.NoError(err) + cp := orig.Clone() + assert.True(proto.Equal(cp.Prompt, orig.Prompt)) + }) + t.Run("not-equal", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(err) + m := TestAuthMethod(t, conn, databaseWrapper, org.PublicId, InactiveState, "alice_rp", "my-dogs-name", + WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), WithApiUrl(TestConvertToUrls(t, "https://api.com")[0])) + orig, err := NewPrompt(ctx, m.PublicId, Consent) + require.NoError(err) + orig2, err := NewPrompt(ctx, m.PublicId, SelectAccount) + require.NoError(err) + + cp := orig.Clone() + assert.True(!proto.Equal(cp.Prompt, orig2.Prompt)) + }) +} + +func TestPrompt_SetTableName(t *testing.T) { + t.Parallel() + defaultTableName := defaultPromptTableName + tests := []struct { + name string + setNameTo string + want string + }{ + { + name: "new-name", + setNameTo: "new-name", + want: "new-name", + }, + { + name: "reset to default", + setNameTo: "", + want: defaultTableName, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + def := AllocPrompt() + require.Equal(defaultTableName, def.TableName()) + m := AllocPrompt() + m.SetTableName(tt.setNameTo) + assert.Equal(tt.want, m.TableName()) + }) + } +} + +func TestPrompt_SupportedPrompt(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + prompt PromptParam + want bool + }{ + { + name: "none-prompt", + prompt: None, + want: true, + }, + { + name: "login-prompt", + prompt: Login, + want: true, + }, + { + name: "consent-prompt", + prompt: Consent, + want: true, + }, + { + name: "select-account-prompt", + prompt: SelectAccount, + want: true, + }, + { + name: "invalid-prompt", + prompt: "invalid", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + got := SupportedPrompt(tt.prompt) + assert.Equal(tt.want, got) + }) + } +} diff --git a/internal/auth/oidc/repository_auth_method_create.go b/internal/auth/oidc/repository_auth_method_create.go index 1a0db12a68..a4f8383811 100644 --- a/internal/auth/oidc/repository_auth_method_create.go +++ b/internal/auth/oidc/repository_auth_method_create.go @@ -16,7 +16,7 @@ import ( // CreateAuthMethod creates am (*AuthMethod) in the repo along with its // associated embedded optional value objects of SigningAlgs, AudClaims, -// and Certificates and returns the newly created AuthMethod +// Prompts, and Certificates and returns the newly created AuthMethod // (with its PublicId set) // // The AuthMethod's public id and version must be empty (zero values). @@ -123,6 +123,13 @@ func (r *Repository) CreateAuthMethod(ctx context.Context, am *AuthMethod, opt . } msgs = append(msgs, accountClaimMapsOplogMsgs...) } + if len(vo.Prompts) > 0 { + promptOplogMsgs := make([]*oplog.Message, 0, len(vo.Prompts)) + if err := w.CreateItems(ctx, vo.Prompts, db.NewOplogMsgs(&promptOplogMsgs)); err != nil { + return err + } + msgs = append(msgs, promptOplogMsgs...) + } metadata := am.oplog(oplog.OpType_OP_TYPE_CREATE) if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) diff --git a/internal/auth/oidc/repository_auth_method_create_test.go b/internal/auth/oidc/repository_auth_method_create_test.go index 1959d99a07..253100f4c7 100644 --- a/internal/auth/oidc/repository_auth_method_create_test.go +++ b/internal/auth/oidc/repository_auth_method_create_test.go @@ -36,6 +36,14 @@ func TestRepository_CreateAuthMethod(t *testing.T) { } return s } + + convertPrompts := func(prompts ...PromptParam) []string { + s := make([]string, 0, len(prompts)) + for _, a := range prompts { + s = append(s, string(a)) + } + return s + } tests := []struct { name string am func(*testing.T) *AuthMethod @@ -48,6 +56,7 @@ func TestRepository_CreateAuthMethod(t *testing.T) { algs := []Alg{RS256, ES256} cbs := TestConvertToUrls(t, "https://www.alice.com/callback")[0] auds := []string{"alice-rp", "bob-rp"} + prompts := []PromptParam{"consent", "select_account"} cert1, pem1 := testGenerateCA(t, "localhost") cert2, pem2 := testGenerateCA(t, "localhost") certs := []*x509.Certificate{cert1, cert2} @@ -65,6 +74,7 @@ func TestRepository_CreateAuthMethod(t *testing.T) { WithName("alice's restaurant"), WithDescription("it's a good place to eat"), WithClaimsScopes("email", "profile"), + WithPrompts(prompts...), WithAccountClaimMap(map[string]AccountToClaim{"display_name": ToNameClaim, "oid": ToSubClaim}), ) require.NoError(t, err) @@ -74,6 +84,7 @@ func TestRepository_CreateAuthMethod(t *testing.T) { require.Equal(t, am.AudClaims, auds) require.Equal(t, am.Certificates, pems) require.Equal(t, am.OperationalState, string(InactiveState)) + require.Equal(t, am.Prompts, convertPrompts(prompts...)) return am }, }, @@ -83,6 +94,7 @@ func TestRepository_CreateAuthMethod(t *testing.T) { algs := []Alg{RS256, ES256} cbs := TestConvertToUrls(t, "https://www.alice.com/callback")[0] auds := []string{"alice-rp-custom", "bob-rp-custom"} + prompts := []PromptParam{"consent", "select_account"} cert1, pem1 := testGenerateCA(t, "localhost") cert2, pem2 := testGenerateCA(t, "localhost") certs := []*x509.Certificate{cert1, cert2} @@ -97,6 +109,7 @@ func TestRepository_CreateAuthMethod(t *testing.T) { WithApiUrl(cbs), WithSigningAlgs(algs...), WithCertificates(certs...), + WithPrompts(prompts...), WithName("alice's restaurant with a twist"), WithDescription("it's an okay but kinda weird place to eat"), WithClaimsScopes("email", "profile"), @@ -109,6 +122,7 @@ func TestRepository_CreateAuthMethod(t *testing.T) { require.Equal(t, am.AudClaims, auds) require.Equal(t, am.Certificates, pems) require.Equal(t, am.OperationalState, string(InactiveState)) + require.Equal(t, am.Prompts, convertPrompts(prompts...)) return am }, opt: []Option{WithPublicId("amoidc_1234567890")}, diff --git a/internal/auth/oidc/repository_auth_method_delete_test.go b/internal/auth/oidc/repository_auth_method_delete_test.go index da841f69e9..fce74a86bc 100644 --- a/internal/auth/oidc/repository_auth_method_delete_test.go +++ b/internal/auth/oidc/repository_auth_method_delete_test.go @@ -41,6 +41,27 @@ func TestRepository_DeleteAuthMethod(t *testing.T) { }(), wantRowsDeleted: 1, }, + { + name: "valid-with-prompts", + authMethod: func() *AuthMethod { + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + return TestAuthMethod( + t, + conn, + databaseWrapper, + org.PublicId, + InactiveState, + "alice_rp", + "alices-dogs-name", + WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), + WithApiUrl(TestConvertToUrls(t, "https://api.com")[0]), + WithPrompts(SelectAccount), + ) + }(), + wantRowsDeleted: 1, + }, { name: "no-public-id", authMethod: func() *AuthMethod { am := AllocAuthMethod(); return &am }(), diff --git a/internal/auth/oidc/repository_auth_method_read.go b/internal/auth/oidc/repository_auth_method_read.go index 8bff4a957d..277b8b5c73 100644 --- a/internal/auth/oidc/repository_auth_method_read.go +++ b/internal/auth/oidc/repository_auth_method_read.go @@ -167,6 +167,9 @@ func (r *Repository) getAuthMethods(ctx context.Context, authMethodId string, sc if agg.AccountClaimMaps != "" { am.AccountClaimMaps = strings.Split(agg.AccountClaimMaps, aggregateDelimiter) } + if agg.Prompts != "" { + am.Prompts = strings.Split(agg.Prompts, aggregateDelimiter) + } authMethods = append(authMethods, &am) } return authMethods, nil @@ -198,6 +201,7 @@ type authMethodAgg struct { Certs string ClaimsScopes string AccountClaimMaps string + Prompts string } // TableName returns the table name for gorm diff --git a/internal/auth/oidc/repository_auth_method_read_test.go b/internal/auth/oidc/repository_auth_method_read_test.go index 77b7de58e5..3ee2fde90d 100644 --- a/internal/auth/oidc/repository_auth_method_read_test.go +++ b/internal/auth/oidc/repository_auth_method_read_test.go @@ -33,7 +33,8 @@ func TestRepository_LookupAuthMethod(t *testing.T) { "alice_rp", "alices-dogs-name", WithAccountClaimMap(map[string]AccountToClaim{"oid": ToSubClaim, "display_name": ToNameClaim}), WithApiUrl(TestConvertToUrls(t, "https://alice-active-priv.com/callback")[0]), - WithSigningAlgs(RS256)) + WithSigningAlgs(RS256), + WithPrompts(Consent, SelectAccount)) amActivePub := TestAuthMethod( t, conn, databaseWrapper, org.PublicId, ActivePublicState, @@ -152,6 +153,32 @@ func TestRepository_ListAuthMethods(t *testing.T) { }, opt: []Option{WithLimit(1), WithOrderByCreateTime(true)}, }, + { + name: "with-prompts", + setupFn: func() ([]string, []*AuthMethod, string) { + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + + am1a := TestAuthMethod( + t, + conn, + databaseWrapper, + org.PublicId, + InactiveState, + "alice_rp", + "alices-dogs-name", + WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), + WithApiUrl(TestConvertToUrls(t, "https://api.com")[0]), + WithClaimsScopes("email", "profile"), + WithPrompts(Consent, SelectAccount), + ) + iam.TestSetPrimaryAuthMethod(t, iamRepo, org, am1a.PublicId) + am1a.IsPrimaryAuthMethod = true + + return []string{am1a.ScopeId}, []*AuthMethod{am1a}, am1a.PublicId + }, + }, { name: "no-search-criteria", setupFn: func() ([]string, []*AuthMethod, string) { diff --git a/internal/auth/oidc/repository_auth_method_update.go b/internal/auth/oidc/repository_auth_method_update.go index c24d184dc7..5a1029373e 100644 --- a/internal/auth/oidc/repository_auth_method_update.go +++ b/internal/auth/oidc/repository_auth_method_update.go @@ -40,6 +40,7 @@ const ( TokenClaimsField = "TokenClaims" UserinfoClaimsField = "UserinfoClaims" KeyIdField = "KeyId" + PromptsField = "Prompts" ) // UpdateAuthMethod will retrieve the auth method from the repository, @@ -59,7 +60,7 @@ const ( // be updated. Fields will be set to NULL if the field is a // zero value and included in fieldMask. Name, Description, Issuer, // ClientId, ClientSecret, MaxAge are all updatable fields. The AuthMethod's -// Value Objects of SigningAlgs, CallbackUrls, AudClaims and Certificates are +// Value Objects of SigningAlgs, Prompts, CallbackUrls, AudClaims and Certificates are // also updatable. if no updatable fields are included in the fieldMaskPaths, // then an error is returned. // @@ -109,6 +110,7 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, am *AuthMethod, versi CertificatesField: am.Certificates, ClaimsScopesField: am.ClaimsScopes, AccountClaimMapsField: am.AccountClaimMaps, + PromptsField: am.Prompts, }, fieldMaskPaths, nil, @@ -177,6 +179,12 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, am *AuthMethod, versi if err != nil { return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) } + + addPrompts, deletePrompts, err := valueObjectChanges(ctx, origAm.PublicId, PromptsVO, am.Prompts, origAm.Prompts, dbMask, nullFields) + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + // we don't allow updates for "sub" claim maps, because we have no way to // determine if the updated "from" claim in the map might create collisions // with any existing account's subject. @@ -196,7 +204,7 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, am *AuthMethod, versi var filteredDbMask, filteredNullFields []string for _, f := range dbMask { switch f { - case SigningAlgsField, AudClaimsField, CertificatesField, ClaimsScopesField, AccountClaimMapsField: + case SigningAlgsField, AudClaimsField, CertificatesField, ClaimsScopesField, AccountClaimMapsField, PromptsField: continue default: filteredDbMask = append(filteredDbMask, f) @@ -204,7 +212,7 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, am *AuthMethod, versi } for _, f := range nullFields { switch f { - case SigningAlgsField, AudClaimsField, CertificatesField, ClaimsScopesField, AccountClaimMapsField: + case SigningAlgsField, AudClaimsField, CertificatesField, ClaimsScopesField, AccountClaimMapsField, PromptsField: continue default: filteredNullFields = append(filteredNullFields, f) @@ -223,7 +231,9 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, am *AuthMethod, versi len(addScopes) == 0 && len(deleteScopes) == 0 && len(addMaps) == 0 && - len(deleteMaps) == 0 { + len(deleteMaps) == 0 && + len(addPrompts) == 0 && + len(deletePrompts) == 0 { return origAm, db.NoRowsAffected, nil } @@ -259,7 +269,7 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, am *AuthMethod, versi db.StdRetryCnt, db.ExpBackoff{}, func(reader db.Reader, w db.Writer) error { - msgs := make([]*oplog.Message, 0, 7) // AuthMethod, Algs*2, Certs*2, Audiences*2 + msgs := make([]*oplog.Message, 0, 9) // AuthMethod, Algs*2, Certs*2, Audiences*2, Prompts*2 ticket, err := w.GetTicket(ctx, am) if err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) @@ -310,6 +320,25 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, am *AuthMethod, versi msgs = append(msgs, addAlgsOplogMsgs...) } + if len(deletePrompts) > 0 { + deletePromptOplogMsgs := make([]*oplog.Message, 0, len(deletePrompts)) + rowsDeleted, err := w.DeleteItems(ctx, deletePrompts, db.NewOplogMsgs(&deletePromptOplogMsgs)) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete prompts")) + } + if rowsDeleted != len(deletePrompts) { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("prompts deleted %d did not match request for %d", rowsDeleted, len(deletePrompts))) + } + msgs = append(msgs, deletePromptOplogMsgs...) + } + if len(addPrompts) > 0 { + addPromptsOplogMsgs := make([]*oplog.Message, 0, len(addPrompts)) + if err := w.CreateItems(ctx, addPrompts, db.NewOplogMsgs(&addPromptsOplogMsgs)); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add prompts")) + } + msgs = append(msgs, addPromptsOplogMsgs...) + } + if len(deleteCerts) > 0 { deleteCertOplogMsgs := make([]*oplog.Message, 0, len(deleteCerts)) rowsDeleted, err := w.DeleteItems(ctx, deleteCerts, db.NewOplogMsgs(&deleteCertOplogMsgs)) @@ -426,12 +455,13 @@ const ( AudClaimVO voName = "AudClaims" ClaimsScopesVO voName = "ClaimsScopes" AccountClaimMapsVO voName = "AccountClaimMaps" + PromptsVO voName = "Prompts" ) // validVoName decides if the name is valid func validVoName(name voName) bool { switch name { - case SigningAlgVO, CertificateVO, AudClaimVO, ClaimsScopesVO, AccountClaimMapsVO: + case SigningAlgVO, CertificateVO, AudClaimVO, ClaimsScopesVO, AccountClaimMapsVO, PromptsVO: return true default: return false @@ -478,6 +508,10 @@ var supportedFactories = map[voName]factoryFunc{ } return NewAccountClaimMap(ctx, publicId, m.From, to) }, + PromptsVO: func(ctx context.Context, publicId string, i any) (any, error) { + str := fmt.Sprintf("%s", i) + return NewPrompt(ctx, publicId, PromptParam(str)) + }, } // valueObjectChanges takes the new and old list of VOs (value objects) and @@ -580,6 +614,7 @@ func validateFieldMask(ctx context.Context, fieldMaskPaths []string) error { case strings.EqualFold(CertificatesField, f): case strings.EqualFold(ClaimsScopesField, f): case strings.EqualFold(AccountClaimMapsField, f): + case strings.EqualFold(PromptsField, f): default: return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("invalid field mask: %s", f)) } @@ -646,6 +681,14 @@ func applyUpdate(new, orig *AuthMethod, fieldMaskPaths []string) *AuthMethod { cp.AccountClaimMaps = make([]string, 0, len(new.AccountClaimMaps)) cp.AccountClaimMaps = append(cp.AccountClaimMaps, new.AccountClaimMaps...) } + case PromptsField: + switch { + case len(new.Prompts) == 0: + cp.Prompts = nil + default: + cp.Prompts = make([]string, 0, len(new.Prompts)) + cp.Prompts = append(cp.Prompts, new.Prompts...) + } } } return cp diff --git a/internal/auth/oidc/repository_auth_method_update_test.go b/internal/auth/oidc/repository_auth_method_update_test.go index 31027d344a..48b77b572d 100644 --- a/internal/auth/oidc/repository_auth_method_update_test.go +++ b/internal/auth/oidc/repository_auth_method_update_test.go @@ -529,6 +529,71 @@ func Test_UpdateAuthMethod(t *testing.T) { version: 2, // since TestAuthMethod(...) did an update to get it to ActivePublicState wantErrMatch: errors.T(errors.InvalidParameter), }, + { + name: "update-with-prompt", + setup: func() *AuthMethod { + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + return TestAuthMethod( + t, + conn, + databaseWrapper, + org.PublicId, + InactiveState, + "alice-rp", + "alice-secret", + WithCertificates(tpCert[0]), + WithSigningAlgs(Alg(tpAlg)), + ) + }, + updateWith: func(orig *AuthMethod) *AuthMethod { + am := AllocAuthMethod() + am.PublicId = orig.PublicId + am.Prompts = []string{string(SelectAccount)} + return &am + }, + fieldMasks: []string{PromptsField}, + version: 1, + want: func(orig, updateWith *AuthMethod) *AuthMethod { + am := orig.Clone() + am.Prompts = updateWith.Prompts + return am + }, + }, + { + name: "update-with-existing-prompt", + setup: func() *AuthMethod { + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + return TestAuthMethod( + t, + conn, + databaseWrapper, + org.PublicId, + InactiveState, + "alice-rp", + "alice-secret", + WithCertificates(tpCert[0]), + WithSigningAlgs(Alg(tpAlg)), + WithPrompts(Consent), + ) + }, + updateWith: func(orig *AuthMethod) *AuthMethod { + am := AllocAuthMethod() + am.PublicId = orig.PublicId + am.Prompts = []string{string(SelectAccount)} + return &am + }, + fieldMasks: []string{PromptsField}, + version: 1, + want: func(orig, updateWith *AuthMethod) *AuthMethod { + am := orig.Clone() + am.Prompts = updateWith.Prompts + return am + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1068,6 +1133,7 @@ func Test_validateFieldMask(t *testing.T) { AudClaimsField, CertificatesField, ClaimsScopesField, + PromptsField, }, }, { @@ -1114,6 +1180,7 @@ func Test_applyUpdate(t *testing.T) { AudClaims: []string{"new-aud-1", "new-aud-2"}, Certificates: []string{"new-pem1", "new-pem-2"}, ClaimsScopes: []string{"new-scope1", "new-scope2"}, + Prompts: []string{string(SelectAccount)}, }, }, orig: &AuthMethod{ @@ -1130,6 +1197,7 @@ func Test_applyUpdate(t *testing.T) { AudClaims: []string{"orig-aud-1", "orig-aud-2"}, Certificates: []string{"orig-pem1", "orig-pem-2"}, ClaimsScopes: []string{"orig-scope1", "orig-scope2"}, + Prompts: []string{string(None)}, }, }, want: &AuthMethod{ @@ -1146,6 +1214,7 @@ func Test_applyUpdate(t *testing.T) { AudClaims: []string{"new-aud-1", "new-aud-2"}, Certificates: []string{"new-pem1", "new-pem-2"}, ClaimsScopes: []string{"new-scope1", "new-scope2"}, + Prompts: []string{string(SelectAccount)}, }, }, fieldMask: []string{ @@ -1160,6 +1229,7 @@ func Test_applyUpdate(t *testing.T) { AudClaimsField, CertificatesField, ClaimsScopesField, + PromptsField, }, }, { @@ -1189,6 +1259,7 @@ func Test_applyUpdate(t *testing.T) { AudClaims: []string{"orig-aud-1", "orig-aud-2"}, Certificates: []string{"orig-pem1", "orig-pem-2"}, ClaimsScopes: []string{"orig-scope1", "orig-scope2"}, + Prompts: []string{string(SelectAccount)}, }, }, want: &AuthMethod{ @@ -1214,6 +1285,7 @@ func Test_applyUpdate(t *testing.T) { AudClaimsField, CertificatesField, ClaimsScopesField, + PromptsField, }, }, } diff --git a/internal/auth/oidc/service_start_auth.go b/internal/auth/oidc/service_start_auth.go index 30078b8230..78adf46381 100644 --- a/internal/auth/oidc/service_start_auth.go +++ b/internal/auth/oidc/service_start_auth.go @@ -118,6 +118,11 @@ func StartAuth(ctx context.Context, oidcRepoFn OidcRepoFactory, authMethodId str oidcOpts = append(oidcOpts, oidc.WithScopes(am.ClaimsScopes...)) } + if len(am.Prompts) > 0 { + prompts := convertToOIDCPrompts(ctx, am.Prompts) + oidcOpts = append(oidcOpts, oidc.WithPrompts(prompts...)) + } + // a bare min oidc.Request needed for the provider.AuthURL(...) call. We've intentionally not populated // things like Audiences, because this oidc.Request isn't cached and not intended for use in future legs // of the authen flow. diff --git a/internal/auth/oidc/service_start_auth_test.go b/internal/auth/oidc/service_start_auth_test.go index 1dfe8c714c..9864dfa1ce 100644 --- a/internal/auth/oidc/service_start_auth_test.go +++ b/internal/auth/oidc/service_start_auth_test.go @@ -33,6 +33,7 @@ func Test_StartAuth(t *testing.T) { _, _, tpAlg, _ := tp.SigningKeys() tpCert, err := ParseCertificates(ctx, tp.CACert()) require.NoError(t, err) + tpPrompt := []PromptParam{Consent, SelectAccount} conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) rootWrapper := db.TestWrapper(t) @@ -77,6 +78,16 @@ func Test_StartAuth(t *testing.T) { WithMaxAge(-1), ) + testAuthMethodWithPrompt := TestAuthMethod( + t, conn, databaseWrapper, org.PublicId, ActivePublicState, + "test-rp4", "fido", + WithIssuer(TestConvertToUrls(t, tp.Addr())[0]), + WithApiUrl(TestConvertToUrls(t, testController.URL)[0]), + WithSigningAlgs(Alg(tpAlg)), + WithCertificates(tpCert...), + WithPrompts(tpPrompt...), + ) + stdSetup := func(am *AuthMethod, repoFn OidcRepoFactory, apiSrv *httptest.Server) (a *AuthMethod, allowedRedirect string) { // update the allowed redirects for the TestProvider tpAllowedRedirect := fmt.Sprintf(CallbackEndpoint, apiSrv.URL) @@ -154,6 +165,13 @@ func Test_StartAuth(t *testing.T) { wantErrMatch: errors.T(errors.RecordNotFound), wantErrContains: "auth method not-valid not found:", }, + { + name: "simple-with-prompt", + repoFn: repoFn, + apiSrv: testController, + authMethod: testAuthMethodWithPrompt, + setup: stdSetup, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/auth/oidc/store/oidc.pb.go b/internal/auth/oidc/store/oidc.pb.go index aea9302212..485701d608 100644 --- a/internal/auth/oidc/store/oidc.pb.go +++ b/internal/auth/oidc/store/oidc.pb.go @@ -125,6 +125,13 @@ type AuthMethod struct { // to_claim. For example "oid=sub". // @inject_tag: `gorm:"-"` AccountClaimMaps []string `protobuf:"bytes,210,rep,name=account_claim_maps,json=accountClaimMaps,proto3" json:"account_claim_maps,omitempty" gorm:"-"` + // prompts are the optional prompts allowed for an oidc auth method. + // These value objects specify whether the authorization server prompts + // the end-user for reauthentication, account selection and consent. + // These are Value Objects that will be stored as Prompt messages, + // and are operatated on as a complete set. + // @inject_tag: `gorm:"-"` + Prompts []string `protobuf:"bytes,220,rep,name=prompts,proto3" json:"prompts,omitempty" gorm:"-"` } func (x *AuthMethod) Reset() { @@ -320,6 +327,13 @@ func (x *AuthMethod) GetAccountClaimMaps() []string { return nil } +func (x *AuthMethod) GetPrompts() []string { + if x != nil { + return x.Prompts + } + return nil +} + // Account represents an OIDC account // the scope_id column is not included here as it is used only to ensure // data integrity in the database between iam users and auth methods. @@ -1041,6 +1055,75 @@ func (x *ManagedGroupMemberAccount) GetMemberId() string { return "" } +// Prompt entries are the prompts allowed for an oidc auth method. +type Prompt struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // @inject_tag: `gorm:"primary_key"` + OidcMethodId string `protobuf:"bytes,10,opt,name=oidc_method_id,json=oidcMethodId,proto3" json:"oidc_method_id,omitempty" gorm:"primary_key"` + // prompt_param is an enum from the auth_oidc_prompt_enm table + // @inject_tag: `gorm:"primary_key;column:prompt"` + PromptParam string `protobuf:"bytes,20,opt,name=prompt_param,json=promptParam,proto3" json:"prompt_param,omitempty" gorm:"primary_key;column:prompt"` + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,30,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` +} + +func (x *Prompt) Reset() { + *x = Prompt{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Prompt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Prompt) ProtoMessage() {} + +func (x *Prompt) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Prompt.ProtoReflect.Descriptor instead. +func (*Prompt) Descriptor() ([]byte, []int) { + return file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDescGZIP(), []int{9} +} + +func (x *Prompt) GetOidcMethodId() string { + if x != nil { + return x.OidcMethodId + } + return "" +} + +func (x *Prompt) GetPromptParam() string { + if x != nil { + return x.PromptParam + } + return "" +} + +func (x *Prompt) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + var File_controller_storage_auth_oidc_store_v1_oidc_proto protoreflect.FileDescriptor var file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDesc = []byte{ @@ -1055,7 +1138,7 @@ var file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDesc = []byte{ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x83, 0x0b, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x4d, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc1, 0x0b, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, @@ -1143,133 +1226,147 @@ var file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDesc = []byte{ 0x6e, 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x4d, 0x61, 0x70, 0x73, 0x12, 0x1d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x52, 0x10, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x4d, 0x61, 0x70, 0x73, 0x22, 0x9a, 0x04, 0x0a, - 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, - 0x74, 0x69, 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, - 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, - 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, - 0x24, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, - 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, - 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x5f, 0x69, 0x64, 0x18, 0x46, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, - 0x72, 0x18, 0x50, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x12, - 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x75, 0x6c, - 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x75, - 0x6c, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, - 0x6e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x21, 0x0a, 0x0c, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x78, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, - 0x28, 0x0a, 0x0f, 0x75, 0x73, 0x65, 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x63, 0x6c, 0x61, 0x69, - 0x6d, 0x73, 0x18, 0x82, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x75, 0x73, 0x65, 0x72, 0x69, - 0x6e, 0x66, 0x6f, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x22, 0x91, 0x01, 0x0a, 0x0a, 0x53, 0x69, - 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x6c, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64, 0x63, - 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x6f, 0x69, 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x10, - 0x0a, 0x03, 0x61, 0x6c, 0x67, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x6c, 0x67, - 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x75, 0x6e, 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x4d, 0x61, 0x70, 0x73, 0x12, 0x3c, 0x0a, 0x07, + 0x70, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x73, 0x18, 0xdc, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x21, + 0xc2, 0xdd, 0x29, 0x1d, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x73, 0x12, 0x12, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x70, 0x74, + 0x73, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x73, 0x22, 0x9a, 0x04, 0x0a, 0x07, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x8f, 0x01, - 0x0a, 0x08, 0x41, 0x75, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, - 0x64, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x69, 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, - 0x12, 0x10, 0x0a, 0x03, 0x61, 0x75, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, - 0x75, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, - 0x94, 0x01, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, - 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x69, 0x64, 0x63, 0x4d, 0x65, 0x74, - 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x14, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, - 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, - 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x96, 0x01, 0x0a, 0x0b, 0x43, 0x6c, 0x61, 0x69, 0x6d, - 0x73, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x6d, + 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, + 0x0c, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x24, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x46, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x18, + 0x50, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x12, 0x18, 0x0a, + 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x75, 0x6c, 0x6c, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x6e, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x78, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x28, 0x0a, + 0x0f, 0x75, 0x73, 0x65, 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, + 0x18, 0x82, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x75, 0x73, 0x65, 0x72, 0x69, 0x6e, 0x66, + 0x6f, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x22, 0x91, 0x01, 0x0a, 0x0a, 0x53, 0x69, 0x67, 0x6e, + 0x69, 0x6e, 0x67, 0x41, 0x6c, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x6f, 0x69, 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, - 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, - 0x70, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, - 0xbe, 0x01, 0x0a, 0x0f, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, - 0x4d, 0x61, 0x70, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x69, 0x64, - 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x72, 0x6f, - 0x6d, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x66, - 0x72, 0x6f, 0x6d, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x6f, 0x5f, 0x63, - 0x6c, 0x61, 0x69, 0x6d, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x6f, 0x43, 0x6c, - 0x61, 0x69, 0x6d, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, - 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, - 0x22, 0xa6, 0x03, 0x0a, 0x0c, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x47, 0x72, 0x6f, 0x75, - 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, - 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x14, 0x20, + 0x6f, 0x69, 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, + 0x61, 0x6c, 0x67, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x6c, 0x67, 0x12, 0x4b, + 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, - 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, - 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x3c, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x75, - 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x46, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, - 0x12, 0x37, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x50, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x1f, 0xc2, 0xdd, 0x29, 0x1b, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x11, - 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x66, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0xaf, 0x01, 0x0a, 0x19, 0x4d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, + 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x8f, 0x01, 0x0a, 0x08, + 0x41, 0x75, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64, 0x63, + 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x6f, 0x69, 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x10, + 0x0a, 0x03, 0x61, 0x75, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x75, 0x64, + 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x94, 0x01, + 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x24, 0x0a, + 0x0e, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x69, 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x54, 0x69, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x5f, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x1b, - 0x0a, 0x09, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x49, 0x64, 0x42, 0x3e, 0x5a, 0x3c, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, - 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x6f, 0x69, 0x64, 0x63, 0x2f, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x54, 0x69, 0x6d, 0x65, 0x22, 0x96, 0x01, 0x0a, 0x0b, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x53, + 0x63, 0x6f, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x6d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x69, + 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xbe, 0x01, + 0x0a, 0x0f, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x4d, 0x61, + 0x70, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x69, 0x64, 0x63, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x72, 0x6f, 0x6d, 0x5f, + 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x66, 0x72, 0x6f, + 0x6d, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x6f, 0x5f, 0x63, 0x6c, 0x61, + 0x69, 0x6d, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x6f, 0x43, 0x6c, 0x61, 0x69, + 0x6d, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xa6, + 0x03, 0x0a, 0x0c, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, + 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, + 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, + 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, + 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x46, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x37, + 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x50, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1f, + 0xc2, 0xdd, 0x29, 0x1b, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x11, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, + 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0xaf, 0x01, 0x0a, 0x19, 0x4d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x5f, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, + 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x49, 0x64, 0x22, 0x9e, 0x01, 0x0a, 0x06, 0x50, 0x72, + 0x6f, 0x6d, 0x70, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x6d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x69, + 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, + 0x6f, 0x6d, 0x70, 0x74, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x12, 0x4b, 0x0a, + 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x6f, 0x69, 0x64, 0x63, 0x2f, 0x73, + 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -1284,7 +1381,7 @@ func file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDescGZIP() []byte return file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDescData } -var file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_controller_storage_auth_oidc_store_v1_oidc_proto_goTypes = []interface{}{ (*AuthMethod)(nil), // 0: controller.storage.auth.oidc.store.v1.AuthMethod (*Account)(nil), // 1: controller.storage.auth.oidc.store.v1.Account @@ -1295,26 +1392,28 @@ var file_controller_storage_auth_oidc_store_v1_oidc_proto_goTypes = []interface{ (*AccountClaimMap)(nil), // 6: controller.storage.auth.oidc.store.v1.AccountClaimMap (*ManagedGroup)(nil), // 7: controller.storage.auth.oidc.store.v1.ManagedGroup (*ManagedGroupMemberAccount)(nil), // 8: controller.storage.auth.oidc.store.v1.ManagedGroupMemberAccount - (*timestamp.Timestamp)(nil), // 9: controller.storage.timestamp.v1.Timestamp + (*Prompt)(nil), // 9: controller.storage.auth.oidc.store.v1.Prompt + (*timestamp.Timestamp)(nil), // 10: controller.storage.timestamp.v1.Timestamp } var file_controller_storage_auth_oidc_store_v1_oidc_proto_depIdxs = []int32{ - 9, // 0: controller.storage.auth.oidc.store.v1.AuthMethod.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 9, // 1: controller.storage.auth.oidc.store.v1.AuthMethod.update_time:type_name -> controller.storage.timestamp.v1.Timestamp - 9, // 2: controller.storage.auth.oidc.store.v1.Account.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 9, // 3: controller.storage.auth.oidc.store.v1.Account.update_time:type_name -> controller.storage.timestamp.v1.Timestamp - 9, // 4: controller.storage.auth.oidc.store.v1.SigningAlg.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 9, // 5: controller.storage.auth.oidc.store.v1.AudClaim.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 9, // 6: controller.storage.auth.oidc.store.v1.Certificate.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 9, // 7: controller.storage.auth.oidc.store.v1.ClaimsScope.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 9, // 8: controller.storage.auth.oidc.store.v1.AccountClaimMap.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 9, // 9: controller.storage.auth.oidc.store.v1.ManagedGroup.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 9, // 10: controller.storage.auth.oidc.store.v1.ManagedGroup.update_time:type_name -> controller.storage.timestamp.v1.Timestamp - 9, // 11: controller.storage.auth.oidc.store.v1.ManagedGroupMemberAccount.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 12, // [12:12] is the sub-list for method output_type - 12, // [12:12] is the sub-list for method input_type - 12, // [12:12] is the sub-list for extension type_name - 12, // [12:12] is the sub-list for extension extendee - 0, // [0:12] is the sub-list for field type_name + 10, // 0: controller.storage.auth.oidc.store.v1.AuthMethod.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 10, // 1: controller.storage.auth.oidc.store.v1.AuthMethod.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 10, // 2: controller.storage.auth.oidc.store.v1.Account.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 10, // 3: controller.storage.auth.oidc.store.v1.Account.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 10, // 4: controller.storage.auth.oidc.store.v1.SigningAlg.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 10, // 5: controller.storage.auth.oidc.store.v1.AudClaim.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 10, // 6: controller.storage.auth.oidc.store.v1.Certificate.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 10, // 7: controller.storage.auth.oidc.store.v1.ClaimsScope.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 10, // 8: controller.storage.auth.oidc.store.v1.AccountClaimMap.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 10, // 9: controller.storage.auth.oidc.store.v1.ManagedGroup.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 10, // 10: controller.storage.auth.oidc.store.v1.ManagedGroup.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 10, // 11: controller.storage.auth.oidc.store.v1.ManagedGroupMemberAccount.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 10, // 12: controller.storage.auth.oidc.store.v1.Prompt.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 13, // [13:13] is the sub-list for method output_type + 13, // [13:13] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_controller_storage_auth_oidc_store_v1_oidc_proto_init() } @@ -1431,6 +1530,18 @@ func file_controller_storage_auth_oidc_store_v1_oidc_proto_init() { return nil } } + file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Prompt); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1438,7 +1549,7 @@ func file_controller_storage_auth_oidc_store_v1_oidc_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDesc, NumEnums: 0, - NumMessages: 9, + NumMessages: 10, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/auth/oidc/testing.go b/internal/auth/oidc/testing.go index 5056bc7b86..fa4e2d33a5 100644 --- a/internal/auth/oidc/testing.go +++ b/internal/auth/oidc/testing.go @@ -39,8 +39,8 @@ import ( const TestFakeManagedGroupFilter = `"/foo" == "bar"` // TestAuthMethod creates a test oidc auth method. WithName, WithDescription, -// WithMaxAge, WithApiUrl, WithIssuer, WithCertificates, WithAudClaims, and -// WithSigningAlgs options are supported. +// WithMaxAge, WithApiUrl, WithIssuer, WithCertificates, WithAudClaims, +// WithSigningAlgs and WithPrompts options are supported. func TestAuthMethod( t testing.TB, conn *db.DB, @@ -123,6 +123,17 @@ func TestAuthMethod( require.NoError(rw.CreateItems(ctx, newAccountClaimMaps)) require.Equal(len(opts.withAccountClaimMap), len(authMethod.AccountClaimMaps)) } + if len(opts.withPrompts) > 0 { + newPrompts := make([]any, 0, len(opts.withPrompts)) + for _, p := range opts.withPrompts { + prompt, err := NewPrompt(ctx, authMethod.PublicId, p) + require.NoError(err) + newPrompts = append(newPrompts, prompt) + } + err := rw.CreateItems(ctx, newPrompts) + require.NoError(err) + require.Equal(len(opts.withPrompts), len(authMethod.Prompts)) + } authMethod.OperationalState = string(state) rowsUpdated, err := rw.Update(ctx, authMethod, []string{OperationalStateField}, nil) require.NoError(err) diff --git a/internal/cmd/commands/authmethodscmd/oidc_funcs.go b/internal/cmd/commands/authmethodscmd/oidc_funcs.go index 5b66e8a79c..be6e288be3 100644 --- a/internal/cmd/commands/authmethodscmd/oidc_funcs.go +++ b/internal/cmd/commands/authmethodscmd/oidc_funcs.go @@ -33,6 +33,7 @@ type extraOidcCmdVars struct { flagAccountClaimMaps []string flagDisableDiscoveredConfigValidation bool flagDryRun bool + flagPrompts []string } const ( @@ -50,6 +51,7 @@ const ( stateFlagName = "state" disableDiscoveredConfigValidationFlagName = "disable-discovered-config-validation" dryRunFlagName = "dry-run" + promptsFlagName = "prompts" ) func extraOidcActionsFlagsMapFuncImpl() map[string][]string { @@ -65,6 +67,7 @@ func extraOidcActionsFlagsMapFuncImpl() map[string][]string { allowedAudienceFlagName, claimsScopes, accountClaimMaps, + promptsFlagName, }, "change-state": { idFlagName, @@ -159,6 +162,12 @@ func extraOidcFlagsFuncImpl(c *OidcCommand, set *base.FlagSets, _ *base.FlagSet) Target: &c.flagDryRun, Usage: "Performs all completeness and validation checks with any newly-provided values without persisting the changes.", }) + case promptsFlagName: + f.StringSliceVar(&base.StringSliceVar{ + Name: promptsFlagName, + Target: &c.flagPrompts, + Usage: "The optional prompt parameter that can be included in the authentication request to control the behavior of the authentication flow.", + }) } } } @@ -283,6 +292,13 @@ func extraOidcFlagHandlingFuncImpl(c *OidcCommand, f *base.FlagSets, opts *[]aut if c.flagDryRun { *opts = append(*opts, authmethods.WithOidcAuthMethodDryRun(c.flagDryRun)) } + switch { + case len(c.flagPrompts) == 0: + case len(c.flagPrompts) == 1 && c.flagPrompts[0] == "null": + *opts = append(*opts, authmethods.DefaultOidcAuthMethodPrompts()) + default: + *opts = append(*opts, authmethods.WithOidcAuthMethodPrompts(c.flagPrompts)) + } return true } diff --git a/internal/daemon/controller/handlers/authmethods/authmethod_service.go b/internal/daemon/controller/handlers/authmethods/authmethod_service.go index 834ca8769a..4d5968c865 100644 --- a/internal/daemon/controller/handlers/authmethods/authmethod_service.go +++ b/internal/daemon/controller/handlers/authmethods/authmethod_service.go @@ -862,6 +862,7 @@ func toAuthMethodProto(ctx context.Context, in auth.AuthMethod, opt ...handlers. AllowedAudiences: i.GetAudClaims(), ClaimsScopes: i.GetClaimsScopes(), AccountClaimMaps: i.GetAccountClaimMaps(), + Prompts: i.GetPrompts(), } if i.DisableDiscoveredConfigValidation { attrs.DisableDiscoveredConfigValidation = true @@ -1031,6 +1032,14 @@ func validateCreateRequest(ctx context.Context, req *pbs.CreateAuthMethodRequest } } } + if len(attrs.GetPrompts()) > 0 { + for _, p := range attrs.GetPrompts() { + if !oidc.SupportedPrompt(oidc.PromptParam(p)) { + badFields[promptsField] = fmt.Sprintf("Contains unsupported prompt %q", p) + break + } + } + } if strings.TrimSpace(attrs.GetApiUrlPrefix().GetValue()) == "" { // TODO: When we start accepting the address used in the request make this an optional field. badFields[apiUrlPrefixField] = "This field is required." @@ -1159,6 +1168,14 @@ func validateUpdateRequest(ctx context.Context, req *pbs.UpdateAuthMethodRequest } } } + if len(attrs.GetPrompts()) > 0 { + for _, p := range attrs.GetPrompts() { + if !oidc.SupportedPrompt(oidc.PromptParam(p)) { + badFields[promptsField] = fmt.Sprintf("Contains unsupported prompt %q", p) + break + } + } + } if len(attrs.GetIdpCaCerts()) > 0 { if _, err := oidc.ParseCertificates(ctx, attrs.GetIdpCaCerts()...); err != nil { badFields[idpCaCertsField] = fmt.Sprintf("Cannot parse CA certificates. %v", err.Error()) diff --git a/internal/daemon/controller/handlers/authmethods/authmethod_service_test.go b/internal/daemon/controller/handlers/authmethods/authmethod_service_test.go index 1e75b98283..dfce01e409 100644 --- a/internal/daemon/controller/handlers/authmethods/authmethod_service_test.go +++ b/internal/daemon/controller/handlers/authmethods/authmethod_service_test.go @@ -143,7 +143,8 @@ func TestGet(t *testing.T) { databaseWrapper, err := kmsCache.GetWrapper(context.Background(), o.GetPublicId(), kms.KeyPurposeDatabase) require.NoError(t, err) oidcam := oidc.TestAuthMethod(t, conn, databaseWrapper, o.GetPublicId(), oidc.InactiveState, "alice_rp", "secret", - oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://alice.com")[0]), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://api.com")[0])) + oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://alice.com")[0]), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://api.com")[0]), + oidc.WithPrompts(oidc.SelectAccount)) wantOidc := &pb.AuthMethod{ Id: oidcam.GetPublicId(), @@ -159,6 +160,7 @@ func TestGet(t *testing.T) { State: string(oidc.InactiveState), ApiUrlPrefix: wrapperspb.String("https://api.com"), CallbackUrl: fmt.Sprintf(oidc.CallbackEndpoint, "https://api.com"), + Prompts: []string{string(oidc.SelectAccount)}, }, }, Version: 1, @@ -303,7 +305,8 @@ func TestList(t *testing.T) { databaseWrapper, err := kmsCache.GetWrapper(context.Background(), oWithAuthMethods.GetPublicId(), kms.KeyPurposeDatabase) require.NoError(t, err) oidcam := oidc.TestAuthMethod(t, conn, databaseWrapper, oWithAuthMethods.GetPublicId(), oidc.ActivePublicState, "alice_rp", "secret", - oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://alice.com")[0]), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://api.com")[0]), oidc.WithSigningAlgs(oidc.EdDSA)) + oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://alice.com")[0]), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://api.com")[0]), oidc.WithSigningAlgs(oidc.EdDSA), + oidc.WithPrompts(oidc.Consent)) iam.TestSetPrimaryAuthMethod(t, iamRepo, oWithAuthMethods, oidcam.GetPublicId()) wantSomeAuthMethods = append(wantSomeAuthMethods, &pb.AuthMethod{ @@ -325,6 +328,7 @@ func TestList(t *testing.T) { SigningAlgorithms: []string{ string(oidc.EdDSA), }, + Prompts: []string{string(oidc.Consent)}, }, }, IsPrimary: true, @@ -533,7 +537,8 @@ func TestDelete(t *testing.T) { databaseWrapper, err := kmsCache.GetWrapper(context.Background(), o.GetPublicId(), kms.KeyPurposeDatabase) require.NoError(t, err) oidcam := oidc.TestAuthMethod(t, conn, databaseWrapper, o.GetPublicId(), oidc.InactiveState, "alice_rp", "my-dogs-name", - oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://alice.com")[0]), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://api.com")[0])) + oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://alice.com")[0]), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://api.com")[0]), + oidc.WithPrompts(oidc.SelectAccount)) ldapAm := ldap.TestAuthMethod(t, conn, databaseWrapper, o.GetPublicId(), []string{"ldaps://ldap1"}) @@ -1368,6 +1373,66 @@ func TestCreate(t *testing.T) { err: handlers.ApiErrorWithCode(codes.InvalidArgument), errContains: "invalid attributes.account_attribute_maps (unable to parse)", }, + { + name: "OIDC AuthMethod With Unsupported Prompt", + req: &pbs.CreateAuthMethodRequest{Item: &pb.AuthMethod{ + ScopeId: o.GetPublicId(), + Type: oidc.Subtype.String(), + Attrs: &pb.AuthMethod_OidcAuthMethodsAttributes{ + OidcAuthMethodsAttributes: &pb.OidcAuthMethodAttributes{ + ApiUrlPrefix: wrapperspb.String("https://api.com"), + Issuer: wrapperspb.String("https://example2.discovery.url:4821"), + ClientId: wrapperspb.String("someclientid"), + ClientSecret: wrapperspb.String("secret"), + Prompts: []string{string(oidc.SelectAccount), "invalid"}, + }, + }, + }}, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + errContains: "Contains unsupported prompt", + }, + { + name: "Create OIDC AuthMethod With Supported Prompt", + req: &pbs.CreateAuthMethodRequest{Item: &pb.AuthMethod{ + ScopeId: o.GetPublicId(), + Type: oidc.Subtype.String(), + Attrs: &pb.AuthMethod_OidcAuthMethodsAttributes{ + OidcAuthMethodsAttributes: &pb.OidcAuthMethodAttributes{ + Issuer: wrapperspb.String("https://example.discovery.url:4821/.well-known/openid-configuration/"), + ClientId: wrapperspb.String("exampleclientid"), + ClientSecret: wrapperspb.String("secret"), + ApiUrlPrefix: wrapperspb.String("https://callback.prefix:9281/path"), + Prompts: []string{string(oidc.SelectAccount)}, + }, + }, + }}, + idPrefix: globals.OidcAuthMethodPrefix + "_", + res: &pbs.CreateAuthMethodResponse{ + Uri: fmt.Sprintf("auth-methods/%s_", globals.OidcAuthMethodPrefix), + Item: &pb.AuthMethod{ + Id: defaultAm.GetPublicId(), + ScopeId: o.GetPublicId(), + CreatedTime: defaultAm.GetCreateTime().GetTimestamp(), + UpdatedTime: defaultAm.GetUpdateTime().GetTimestamp(), + Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: o.GetType(), ParentScopeId: scope.Global.String()}, + Version: 1, + Type: oidc.Subtype.String(), + Attrs: &pb.AuthMethod_OidcAuthMethodsAttributes{ + OidcAuthMethodsAttributes: &pb.OidcAuthMethodAttributes{ + Issuer: wrapperspb.String("https://example.discovery.url:4821/"), + ClientId: wrapperspb.String("exampleclientid"), + ClientSecretHmac: "", + State: string(oidc.InactiveState), + ApiUrlPrefix: wrapperspb.String("https://callback.prefix:9281/path"), + CallbackUrl: "https://callback.prefix:9281/path/v1/auth-methods/oidc:authenticate:callback", + Prompts: []string{string(oidc.SelectAccount)}, + }, + }, + AuthorizedActions: oidcAuthorizedActions, + AuthorizedCollectionActions: authorizedCollectionActions, + }, + }, + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { diff --git a/internal/daemon/controller/handlers/authmethods/oidc.go b/internal/daemon/controller/handlers/authmethods/oidc.go index 1cef993e48..df7d4abaa3 100644 --- a/internal/daemon/controller/handlers/authmethods/oidc.go +++ b/internal/daemon/controller/handlers/authmethods/oidc.go @@ -46,6 +46,7 @@ const ( codeField = "attributes.code" claimsScopesField = "attributes.claims_scopes" accountClaimMapsField = "attributes.account_claim_maps" + promptsField = "attributes.prompts" ) var oidcMaskManager handlers.MaskManager @@ -445,6 +446,13 @@ func toStorageOidcAuthMethod(ctx context.Context, scopeId string, in *pb.AuthMet if len(signAlgs) > 0 { opts = append(opts, oidc.WithSigningAlgs(signAlgs...)) } + var prompts []oidc.PromptParam + for _, a := range attrs.GetPrompts() { + prompts = append(prompts, oidc.PromptParam(a)) + } + if len(prompts) > 0 { + opts = append(opts, oidc.WithPrompts(prompts...)) + } if len(attrs.GetAllowedAudiences()) > 0 { opts = append(opts, oidc.WithAudClaims(attrs.GetAllowedAudiences()...)) } diff --git a/internal/daemon/controller/handlers/authmethods/oidc_test.go b/internal/daemon/controller/handlers/authmethods/oidc_test.go index 4eb04cad25..a60275cf5b 100644 --- a/internal/daemon/controller/handlers/authmethods/oidc_test.go +++ b/internal/daemon/controller/handlers/authmethods/oidc_test.go @@ -128,6 +128,7 @@ func getSetup(t *testing.T) setup { oidc.WithApiUrl(oidc.TestConvertToUrls(t, ret.testController.URL)[0]), oidc.WithSigningAlgs(oidc.Alg(ret.testProviderAlg)), oidc.WithCertificates(ret.testProviderCaCert...), + oidc.WithPrompts(oidc.Consent), ) ret.testProviderAllowedRedirect = fmt.Sprintf(oidc.CallbackEndpoint, ret.testController.URL) @@ -281,6 +282,7 @@ func TestUpdate_OIDC(t *testing.T) { tpClientSecret := "her-dog's-name" tp.SetClientCreds(tpClientId, tpClientSecret) _, _, tpAlg, _ := tp.SigningKeys() + tpPrompt := capoidc.None defaultAttributes := &pb.AuthMethod_OidcAuthMethodsAttributes{ OidcAuthMethodsAttributes: &pb.OidcAuthMethodAttributes{ @@ -290,6 +292,7 @@ func TestUpdate_OIDC(t *testing.T) { ApiUrlPrefix: wrapperspb.String("https://example.com"), IdpCaCerts: []string{tp.CACert()}, SigningAlgorithms: []string{string(tpAlg)}, + Prompts: []string{string(tpPrompt)}, }, } defaultReadAttributes := &pb.AuthMethod_OidcAuthMethodsAttributes{ @@ -302,6 +305,7 @@ func TestUpdate_OIDC(t *testing.T) { CallbackUrl: "https://example.com/v1/auth-methods/oidc:authenticate:callback", IdpCaCerts: []string{tp.CACert()}, SigningAlgorithms: []string{string(tpAlg)}, + Prompts: []string{string(tpPrompt)}, }, } @@ -995,6 +999,53 @@ func TestUpdate_OIDC(t *testing.T) { }, }, }, + { + name: "Unsupported Prompts", + req: &pbs.UpdateAuthMethodRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"attributes.prompts"}, + }, + Item: &pb.AuthMethod{ + Attrs: &pb.AuthMethod_OidcAuthMethodsAttributes{ + OidcAuthMethodsAttributes: &pb.OidcAuthMethodAttributes{ + Prompts: []string{string("invalid")}, + }, + }, + }, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Update Prompt With Valid Values", + req: &pbs.UpdateAuthMethodRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"attributes.prompts"}, + }, + Item: &pb.AuthMethod{ + Attrs: &pb.AuthMethod_OidcAuthMethodsAttributes{ + OidcAuthMethodsAttributes: &pb.OidcAuthMethodAttributes{ + Prompts: []string{string(oidc.Consent), string(oidc.SelectAccount)}, + }, + }, + }, + }, + res: &pbs.UpdateAuthMethodResponse{ + Item: &pb.AuthMethod{ + ScopeId: o.GetPublicId(), + Name: &wrapperspb.StringValue{Value: "default"}, + Description: &wrapperspb.StringValue{Value: "default"}, + Type: oidc.Subtype.String(), + Attrs: func() *pb.AuthMethod_OidcAuthMethodsAttributes { + f := proto.Clone(defaultReadAttributes.OidcAuthMethodsAttributes).(*pb.OidcAuthMethodAttributes) + f.Prompts = []string{string(oidc.Consent), string(oidc.SelectAccount)} + return &pb.AuthMethod_OidcAuthMethodsAttributes{OidcAuthMethodsAttributes: f} + }(), + Scope: defaultScopeInfo, + AuthorizedActions: oidcAuthorizedActions, + AuthorizedCollectionActions: authorizedCollectionActions, + }, + }, + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { diff --git a/internal/db/schema/migrations/oss/postgres/56/02_add_data_key_foreign_key_references.up.sql b/internal/db/schema/migrations/oss/postgres/56/02_add_data_key_foreign_key_references.up.sql index 3e85d65563..e3e8f28777 100644 --- a/internal/db/schema/migrations/oss/postgres/56/02_add_data_key_foreign_key_references.up.sql +++ b/internal/db/schema/migrations/oss/postgres/56/02_add_data_key_foreign_key_references.up.sql @@ -101,6 +101,8 @@ begin; alter table credential_static_ssh_private_key_credential alter column key_id type kms_private_id; + +-- Recreated in 79/01_auth_oidc_prompt.up.sql create view oidc_auth_method_with_value_obj as select case when s.primary_auth_method_id is not null then diff --git a/internal/db/schema/migrations/oss/postgres/79/01_auth_oidc_prompt.up.sql b/internal/db/schema/migrations/oss/postgres/79/01_auth_oidc_prompt.up.sql new file mode 100644 index 0000000000..5f45ba8266 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/79/01_auth_oidc_prompt.up.sql @@ -0,0 +1,112 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: BUSL-1.1 + +begin; + +-- auth_oidc_prompt_enm entries define the supported oidc auth method prompts. +create table auth_oidc_prompt_enm ( + name text primary key + constraint only_predefined_auth_oidc_prompts_allowed + check ( + name in ( + 'none', + 'login', + 'consent', + 'select_account' + ) + ) +); + + -- define the immutable fields for auth_oidc_prompt_enm (all of them) +create trigger immutable_columns before update on auth_oidc_prompt_enm + for each row execute procedure immutable_columns('name'); + +insert into auth_oidc_prompt_enm (name) values + ('none'), + ('login'), + ('consent'), + ('select_account'); + +-- auth_oidc_prompt entries are the prompts allowed for an oidc auth method. +create table auth_oidc_prompt ( + create_time wt_timestamp, + oidc_method_id wt_public_id + constraint auth_oidc_method_fkey + references auth_oidc_method(public_id) + on delete cascade + on update cascade, + prompt text + constraint auth_oidc_prompt_enm_fkey + references auth_oidc_prompt_enm(name) + on delete restrict + on update cascade, + primary key(oidc_method_id, prompt) +); +comment on table auth_oidc_prompt is + 'auth_oidc_prompt entries are the prompts allowed for an oidc auth method.'; + +create trigger immutable_columns before update on auth_oidc_prompt + for each row execute procedure immutable_columns('create_time', 'oidc_method_id', 'prompt'); + +create trigger + default_create_time_column +before +insert on auth_oidc_prompt + for each row execute procedure default_create_time(); + +-- we will drop the oidc_auth_method_with_value_obj view, so we can recreate it +-- and add the oidc prompts to the returned set. +drop view oidc_auth_method_with_value_obj; + +-- oidc_auth_method_with_value_obj is useful for reading an oidc auth method +-- with its associated value objects (algs, auds, certs, claims scopes, +-- account claim maps and prompts) as columns with | delimited values. The +-- use of the postgres string_agg(...) to aggregate the value objects into a +-- column works because we are only pulling in one column from the associated +-- tables and that value is part of the primary key and unique. This view +-- will make things like recursive listing of oidc auth methods fairly +-- straightforward to implement for the oidc repo. The view also includes an +-- is_primary_auth_method bool +create view oidc_auth_method_with_value_obj as +select + case when s.primary_auth_method_id is not null then + true + else false end + as is_primary_auth_method, + am.public_id, + am.scope_id, + am.name, + am.description, + am.create_time, + am.update_time, + am.version, + am.state, + am.api_url, + am.disable_discovered_config_validation, + am.issuer, + am.client_id, + am.client_secret, + am.client_secret_hmac, + am.key_id, + am.max_age, + -- the string_agg(..) column will be null if there are no associated value objects + string_agg(distinct alg.signing_alg_name, '|') as algs, + string_agg(distinct aud.aud_claim, '|') as auds, + string_agg(distinct cert.certificate, '|') as certs, + string_agg(distinct cs.scope, '|') as claims_scopes, + string_agg(distinct p.prompt, '|') as prompts, + string_agg(distinct concat_ws('=', acm.from_claim, acm.to_claim), '|') as account_claim_maps +from + auth_oidc_method am + left outer join iam_scope s on am.public_id = s.primary_auth_method_id + left outer join auth_oidc_signing_alg alg on am.public_id = alg.oidc_method_id + left outer join auth_oidc_aud_claim aud on am.public_id = aud.oidc_method_id + left outer join auth_oidc_certificate cert on am.public_id = cert.oidc_method_id + left outer join auth_oidc_scope cs on am.public_id = cs.oidc_method_id + left outer join auth_oidc_account_claim_map acm on am.public_id = acm.oidc_method_id + left outer join auth_oidc_prompt p on am.public_id = p.oidc_method_id +group by am.public_id, is_primary_auth_method; -- there can be only one public_id + is_primary_auth_method, so group by isn't a problem. +comment on view oidc_auth_method_with_value_obj is +'oidc auth method with its associated value objects (algs, auds, certs, scopes, prompts) as columns with | delimited values'; + +commit; \ No newline at end of file diff --git a/internal/db/sqltest/Makefile b/internal/db/sqltest/Makefile index 8ef54093d4..3dafeebca3 100644 --- a/internal/db/sqltest/Makefile +++ b/internal/db/sqltest/Makefile @@ -32,7 +32,8 @@ TESTS ?= tests/setup/*.sql \ tests/storage/*.sql \ tests/domain/*.sql \ tests/history/*.sql \ - tests/recording/*.sql + tests/recording/*.sql \ + tests/auth/*/*.sql POSTGRES_DOCKER_IMAGE_BASE ?= postgres diff --git a/internal/db/sqltest/tests/auth/oidc/prompt.sql b/internal/db/sqltest/tests/auth/oidc/prompt.sql new file mode 100644 index 0000000000..253f801af7 --- /dev/null +++ b/internal/db/sqltest/tests/auth/oidc/prompt.sql @@ -0,0 +1,63 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: BUSL-1.1 + +begin; + select plan(5); + select wtt_load('widgets', 'iam', 'kms', 'auth'); + + -- Try to insert invalid auth_oidc_prompt value to test constraint + prepare invalid_auth_oidc_prompt_value as + insert into auth_oidc_prompt + (oidc_method_id, prompt) + values + ('aom___widget', 'invalid'); + + select throws_ok( + 'invalid_auth_oidc_prompt_value', + '23503', + 'insert or update on table "auth_oidc_prompt" violates foreign key constraint "auth_oidc_prompt_enm_fkey"', + 'inserting a row with invalid auth_oidc_prompt value' + ); + + -- Insert valid valid_auth_oidc_prompt_value value to test constraint with a valid value + prepare valid_auth_oidc_prompt_value as + insert into auth_oidc_prompt + (oidc_method_id, prompt) + values + ('aom___widget', 'select_account'); + select lives_ok('valid_auth_oidc_prompt_value'); + + -- Update immutable prompt field + prepare update_auth_oidc_prompt_value as + update auth_oidc_prompt + set prompt = 'consent' + where oidc_method_id = 'aom___widget'; + + select throws_ok( + 'update_auth_oidc_prompt_value', + '23601', + 'immutable column: auth_oidc_prompt.prompt', + 'updating an immutable auth_oidc_prompt column' + ); + + -- validate oidc_auth_method_with_value_obj view + select has_view('oidc_auth_method_with_value_obj', 'view for reading an oidc auth method with its associated value objects does not exist'); + + insert into auth_oidc_prompt + (oidc_method_id, prompt) + values + ('aom___widget', 'consent'); + + prepare select_oidc_auth_method_aggregate as + select public_id::text, prompts::text + from oidc_auth_method_with_value_obj + where public_id = 'aom___widget'; + + select results_eq( + 'select_oidc_auth_method_aggregate', + $$VALUES + ('aom___widget', 'consent|select_account')$$ + ); + + select * from finish(); +rollback; \ No newline at end of file diff --git a/internal/proto/controller/api/resources/authmethods/v1/auth_method.proto b/internal/proto/controller/api/resources/authmethods/v1/auth_method.proto index cc22f2b7ce..ad4fe60bb1 100644 --- a/internal/proto/controller/api/resources/authmethods/v1/auth_method.proto +++ b/internal/proto/controller/api/resources/authmethods/v1/auth_method.proto @@ -264,6 +264,16 @@ message OidcAuthMethodAttributes { json_name = "dry_run", (custom_options.v1.generate_sdk_option) = true ]; // @gotags: `class:"public"` + + // The prompts allowed for the auth method. + repeated string prompts = 140 [ + json_name = "prompts", + (custom_options.v1.generate_sdk_option) = true, + (custom_options.v1.mask_mapping) = { + this: "attributes.prompts" + that: "Prompts" + } + ]; // @gotags: `class:"public"` } // The structure of the OIDC authenticate start response, in the JSON object diff --git a/internal/proto/controller/storage/auth/oidc/store/v1/oidc.proto b/internal/proto/controller/storage/auth/oidc/store/v1/oidc.proto index 3922897db1..f6558cc034 100644 --- a/internal/proto/controller/storage/auth/oidc/store/v1/oidc.proto +++ b/internal/proto/controller/storage/auth/oidc/store/v1/oidc.proto @@ -163,6 +163,17 @@ message AuthMethod { this: "AccountClaimMaps" that: "attributes.account_claim_maps" }]; + + // prompts are the optional prompts allowed for an oidc auth method. + // These value objects specify whether the authorization server prompts + // the end-user for reauthentication, account selection and consent. + // These are Value Objects that will be stored as Prompt messages, + // and are operatated on as a complete set. + // @inject_tag: `gorm:"-"` + repeated string prompts = 220 [(custom_options.v1.mask_mapping) = { + this: "Prompts" + that: "attributes.prompts" + }]; } // Account represents an OIDC account @@ -364,3 +375,17 @@ message ManagedGroupMemberAccount { // @inject_tag: `gorm:"primary_key"` string member_id = 30; } + +// Prompt entries are the prompts allowed for an oidc auth method. +message Prompt { + // @inject_tag: `gorm:"primary_key"` + string oidc_method_id = 10; + + // prompt_param is an enum from the auth_oidc_prompt_enm table + // @inject_tag: `gorm:"primary_key;column:prompt"` + string prompt_param = 20; + + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 30; +} diff --git a/sdk/pbs/controller/api/resources/authmethods/auth_method.pb.go b/sdk/pbs/controller/api/resources/authmethods/auth_method.pb.go index fe8df0445f..eb76ea77af 100644 --- a/sdk/pbs/controller/api/resources/authmethods/auth_method.pb.go +++ b/sdk/pbs/controller/api/resources/authmethods/auth_method.pb.go @@ -372,6 +372,8 @@ type OidcAuthMethodAttributes struct { // along with the updated fields applied to the resource (but not persisted) as // a result of the update request. DryRun bool `protobuf:"varint,130,opt,name=dry_run,proto3" json:"dry_run,omitempty" class:"public"` // @gotags: `class:"public"` + // The prompts allowed for the auth method. + Prompts []string `protobuf:"bytes,140,rep,name=prompts,proto3" json:"prompts,omitempty" class:"public"` // @gotags: `class:"public"` } func (x *OidcAuthMethodAttributes) Reset() { @@ -511,6 +513,13 @@ func (x *OidcAuthMethodAttributes) GetDryRun() bool { return false } +func (x *OidcAuthMethodAttributes) GetPrompts() []string { + if x != nil { + return x.Prompts + } + return nil +} + // The structure of the OIDC authenticate start response, in the JSON object type OidcAuthMethodAuthenticateStartResponse struct { state protoimpl.MessageState @@ -1244,7 +1253,7 @@ var file_controller_api_resources_authmethods_v1_auth_method_proto_rawDesc = []b 0x6e, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x11, 0x4d, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x52, 0x13, 0x6d, 0x69, 0x6e, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, - 0x72, 0x64, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0xe6, 0x09, 0x0a, 0x18, 0x4f, 0x69, + 0x72, 0x64, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0xa8, 0x0a, 0x0a, 0x18, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x59, 0x0a, 0x06, @@ -1323,201 +1332,205 @@ var file_controller_api_resources_authmethods_v1_auth_method_proto_rawDesc = []b 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x82, 0x01, 0x20, 0x01, 0x28, 0x08, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, - 0x75, 0x6e, 0x22, 0x61, 0x0a, 0x27, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, - 0x74, 0x68, 0x6f, 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, - 0x08, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x22, 0xb7, 0x01, 0x0a, 0x29, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, - 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x12, 0x2c, 0x0a, 0x11, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x32, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x75, 0x72, 0x69, 0x22, - 0x5c, 0x0a, 0x2a, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, - 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, - 0x12, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, - 0x75, 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x66, 0x69, 0x6e, 0x61, 0x6c, - 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x22, 0x44, 0x0a, - 0x26, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, - 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x5f, 0x69, 0x64, 0x22, 0x41, 0x0a, 0x27, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xb9, 0x13, 0x0a, 0x18, 0x4c, 0x64, 0x61, 0x70, 0x41, - 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x2c, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x10, 0x61, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, - 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x46, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x5f, 0x74, 0x6c, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x42, 0x28, 0xa0, 0xda, 0x29, 0x01, + 0x75, 0x6e, 0x12, 0x40, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x8c, 0x01, + 0x20, 0x03, 0x28, 0x09, 0x42, 0x25, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1d, 0x0a, 0x12, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x70, + 0x74, 0x73, 0x12, 0x07, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x73, 0x52, 0x07, 0x70, 0x72, 0x6f, + 0x6d, 0x70, 0x74, 0x73, 0x22, 0x61, 0x0a, 0x27, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68, + 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x22, 0xb7, 0x01, 0x0a, 0x29, 0x4f, 0x69, 0x64, 0x63, + 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2c, 0x0a, 0x11, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x11, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x75, 0x72, 0x69, + 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x75, 0x72, + 0x69, 0x22, 0x5c, 0x0a, 0x2a, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, + 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2e, 0x0a, 0x12, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x66, 0x69, 0x6e, + 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x22, + 0x44, 0x0a, 0x26, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x22, 0x41, 0x0a, 0x27, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, + 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xb9, 0x13, 0x0a, 0x18, 0x4c, 0x64, 0x61, + 0x70, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x2c, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x10, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x46, 0x0a, 0x09, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x5f, 0x74, 0x6c, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x42, 0x28, 0xa0, 0xda, + 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x20, 0x0a, 0x14, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x73, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x6c, 0x73, 0x12, 0x08, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x54, 0x6c, 0x73, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x6c, + 0x73, 0x12, 0x52, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, 0x74, 0x6c, + 0x73, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x08, 0x42, 0x2e, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, + 0x26, 0x0a, 0x17, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x69, 0x6e, + 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, 0x74, 0x6c, 0x73, 0x12, 0x0b, 0x49, 0x6e, 0x73, 0x65, + 0x63, 0x75, 0x72, 0x65, 0x54, 0x6c, 0x73, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, + 0x65, 0x5f, 0x74, 0x6c, 0x73, 0x12, 0x4e, 0x0a, 0x0b, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, + 0x72, 0x5f, 0x64, 0x6e, 0x18, 0x28, 0x20, 0x01, 0x28, 0x08, 0x42, 0x2c, 0xa0, 0xda, 0x29, 0x01, + 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x6e, 0x12, 0x0a, 0x44, 0x69, + 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x6e, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, + 0x65, 0x72, 0x5f, 0x64, 0x6e, 0x12, 0x65, 0x0a, 0x11, 0x61, 0x6e, 0x6f, 0x6e, 0x5f, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x32, 0x20, 0x01, 0x28, 0x08, + 0x42, 0x37, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x2f, 0x0a, 0x1c, 0x61, 0x74, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x61, 0x6e, 0x6f, 0x6e, 0x5f, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x0f, 0x41, 0x6e, 0x6f, 0x6e, 0x47, 0x72, + 0x6f, 0x75, 0x70, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x11, 0x61, 0x6e, 0x6f, 0x6e, 0x5f, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x68, 0x0a, 0x0a, + 0x75, 0x70, 0x6e, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x2a, + 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x22, 0x0a, 0x15, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x70, 0x6e, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, + 0x09, 0x55, 0x70, 0x6e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x0a, 0x75, 0x70, 0x6e, 0x5f, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x33, 0x0a, 0x04, 0x75, 0x72, 0x6c, 0x73, 0x18, 0x46, + 0x20, 0x03, 0x28, 0x09, 0x42, 0x1f, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x17, 0x0a, 0x0f, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x72, 0x6c, 0x73, 0x12, + 0x04, 0x55, 0x72, 0x6c, 0x73, 0x52, 0x04, 0x75, 0x72, 0x6c, 0x73, 0x12, 0x5c, 0x0a, 0x07, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x64, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x24, 0xa0, 0xda, 0x29, 0x01, + 0xc2, 0xdd, 0x29, 0x1c, 0x0a, 0x12, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x64, 0x6e, 0x12, 0x06, 0x55, 0x73, 0x65, 0x72, 0x44, 0x6e, + 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x64, 0x6e, 0x12, 0x64, 0x0a, 0x09, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x28, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x20, 0x0a, 0x14, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, - 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x6c, 0x73, 0x12, 0x08, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x54, 0x6c, 0x73, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x6c, 0x73, 0x12, - 0x52, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, 0x74, 0x6c, 0x73, 0x18, - 0x1e, 0x20, 0x01, 0x28, 0x08, 0x42, 0x2e, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x26, 0x0a, - 0x17, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x69, 0x6e, 0x73, 0x65, - 0x63, 0x75, 0x72, 0x65, 0x5f, 0x74, 0x6c, 0x73, 0x12, 0x0b, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, - 0x72, 0x65, 0x54, 0x6c, 0x73, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, - 0x74, 0x6c, 0x73, 0x12, 0x4e, 0x0a, 0x0b, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, - 0x64, 0x6e, 0x18, 0x28, 0x20, 0x01, 0x28, 0x08, 0x42, 0x2c, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, - 0x29, 0x24, 0x0a, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x64, - 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x6e, 0x12, 0x0a, 0x44, 0x69, 0x73, 0x63, - 0x6f, 0x76, 0x65, 0x72, 0x44, 0x6e, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, - 0x5f, 0x64, 0x6e, 0x12, 0x65, 0x0a, 0x11, 0x61, 0x6e, 0x6f, 0x6e, 0x5f, 0x67, 0x72, 0x6f, 0x75, - 0x70, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x32, 0x20, 0x01, 0x28, 0x08, 0x42, 0x37, - 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x2f, 0x0a, 0x1c, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x65, 0x73, 0x2e, 0x61, 0x6e, 0x6f, 0x6e, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, - 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x0f, 0x41, 0x6e, 0x6f, 0x6e, 0x47, 0x72, 0x6f, 0x75, - 0x70, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x11, 0x61, 0x6e, 0x6f, 0x6e, 0x5f, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x68, 0x0a, 0x0a, 0x75, 0x70, - 0x6e, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x2a, 0xa0, 0xda, - 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x22, 0x0a, 0x15, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x65, 0x73, 0x2e, 0x75, 0x70, 0x6e, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x09, 0x55, - 0x70, 0x6e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x0a, 0x75, 0x70, 0x6e, 0x5f, 0x64, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x33, 0x0a, 0x04, 0x75, 0x72, 0x6c, 0x73, 0x18, 0x46, 0x20, 0x03, - 0x28, 0x09, 0x42, 0x1f, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x17, 0x0a, 0x0f, 0x61, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x72, 0x6c, 0x73, 0x12, 0x04, 0x55, - 0x72, 0x6c, 0x73, 0x52, 0x04, 0x75, 0x72, 0x6c, 0x73, 0x12, 0x5c, 0x0a, 0x07, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x64, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x24, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, - 0x29, 0x1c, 0x0a, 0x12, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x64, 0x6e, 0x12, 0x06, 0x55, 0x73, 0x65, 0x72, 0x44, 0x6e, 0x52, 0x07, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x64, 0x6e, 0x12, 0x64, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x61, 0x74, 0x74, 0x72, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x28, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, - 0x29, 0x20, 0x0a, 0x14, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x12, 0x08, 0x55, 0x73, 0x65, 0x72, 0x41, 0x74, - 0x74, 0x72, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x12, 0x6c, 0x0a, - 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x64, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x42, 0x2c, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x16, 0x61, 0x74, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, - 0x65, 0x72, 0x12, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x0b, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x56, 0x0a, 0x0d, 0x65, - 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x6e, 0x20, 0x01, - 0x28, 0x08, 0x42, 0x30, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x28, 0x0a, 0x18, 0x61, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x0c, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x73, 0x52, 0x0d, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x73, 0x12, 0x60, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x64, 0x6e, 0x18, - 0x78, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x42, 0x26, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1e, 0x0a, 0x13, 0x61, - 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, - 0x64, 0x6e, 0x12, 0x07, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x44, 0x6e, 0x52, 0x08, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x5f, 0x64, 0x6e, 0x12, 0x69, 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x61, - 0x74, 0x74, 0x72, 0x18, 0x82, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x2a, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, - 0x29, 0x22, 0x0a, 0x15, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x67, - 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x12, 0x09, 0x47, 0x72, 0x6f, 0x75, 0x70, - 0x41, 0x74, 0x74, 0x72, 0x52, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x61, 0x74, 0x74, 0x72, - 0x12, 0x71, 0x0a, 0x0c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x18, 0x8c, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x12, 0x08, 0x55, 0x73, 0x65, 0x72, + 0x41, 0x74, 0x74, 0x72, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x12, + 0x6c, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x64, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x42, 0x2c, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x16, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x66, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x12, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x52, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x56, 0x0a, + 0x0d, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x6e, + 0x20, 0x01, 0x28, 0x08, 0x42, 0x30, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x28, 0x0a, 0x18, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x0c, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x0d, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x60, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x64, + 0x6e, 0x18, 0x78, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x2e, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x26, 0x0a, - 0x17, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, - 0x70, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x0b, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x46, - 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x0c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x66, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x12, 0x54, 0x0a, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x73, 0x18, 0x96, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x2f, 0xa0, 0xda, 0x29, 0x01, - 0xc2, 0xdd, 0x29, 0x27, 0x0a, 0x17, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, - 0x2e, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x0c, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x52, 0x0c, 0x63, 0x65, 0x72, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x89, 0x01, 0x0a, 0x12, 0x63, 0x6c, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x26, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1e, 0x0a, + 0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x5f, 0x64, 0x6e, 0x12, 0x07, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x44, 0x6e, 0x52, 0x08, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x64, 0x6e, 0x12, 0x69, 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x5f, 0x61, 0x74, 0x74, 0x72, 0x18, 0x82, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x2a, 0xa0, 0xda, 0x29, 0x01, + 0xc2, 0xdd, 0x29, 0x22, 0x0a, 0x15, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x12, 0x09, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x41, 0x74, 0x74, 0x72, 0x52, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x61, 0x74, + 0x74, 0x72, 0x12, 0x71, 0x0a, 0x0c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x18, 0x8c, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x2e, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, + 0x26, 0x0a, 0x17, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x0b, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x0c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x54, 0x0a, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x73, 0x18, 0x96, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x2f, 0xa0, 0xda, + 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x27, 0x0a, 0x17, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x73, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, + 0x0c, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x52, 0x0c, 0x63, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x89, 0x01, 0x0a, 0x12, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x18, 0xa0, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x3a, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, + 0x32, 0x0a, 0x1d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x18, 0xa0, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x3a, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x32, 0x0a, - 0x1d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x11, - 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x98, 0x01, 0x0a, 0x16, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0xaa, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x41, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x39, 0x0a, - 0x21, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, + 0x12, 0x11, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x98, 0x01, 0x0a, 0x16, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, - 0x65, 0x79, 0x12, 0x14, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x16, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x65, 0x79, 0x18, 0xaa, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x41, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, + 0x39, 0x0a, 0x21, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x16, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, + 0x65, 0x79, 0x12, 0x41, 0x0a, 0x1b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x6d, 0x61, + 0x63, 0x18, 0xb4, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, - 0x12, 0x41, 0x0a, 0x1b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, - 0xb4, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, - 0x6d, 0x61, 0x63, 0x12, 0x5d, 0x0a, 0x07, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x64, 0x6e, 0x18, 0xbe, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x42, 0x24, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1c, 0x0a, 0x12, 0x61, - 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x64, - 0x6e, 0x12, 0x06, 0x42, 0x69, 0x6e, 0x64, 0x44, 0x6e, 0x52, 0x07, 0x62, 0x69, 0x6e, 0x64, 0x5f, - 0x64, 0x6e, 0x12, 0x75, 0x0a, 0x0d, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x18, 0xc8, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x30, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, - 0x29, 0x28, 0x0a, 0x18, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x62, - 0x69, 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x0c, 0x42, 0x69, - 0x6e, 0x64, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x0d, 0x62, 0x69, 0x6e, 0x64, - 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x2f, 0x0a, 0x12, 0x62, 0x69, 0x6e, - 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, - 0xd2, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x73, - 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x12, 0x62, 0x0a, 0x10, 0x75, 0x73, - 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0xdc, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x42, 0x35, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x2d, 0x0a, - 0x1b, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x5f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x0e, 0x55, 0x73, - 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x10, 0x75, 0x73, - 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x7a, - 0x0a, 0x16, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0xe6, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, - 0x41, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x39, 0x0a, 0x21, 0x61, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x12, 0x14, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x4d, 0x61, - 0x70, 0x73, 0x52, 0x16, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x74, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x12, 0x66, 0x0a, 0x11, 0x6d, 0x61, - 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, - 0xf0, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x37, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x2f, - 0x0a, 0x1c, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x6d, 0x61, 0x78, - 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x0f, - 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x52, - 0x11, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, - 0x7a, 0x65, 0x12, 0x8d, 0x01, 0x0a, 0x13, 0x64, 0x65, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, - 0x63, 0x65, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0xfa, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, - 0x3c, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x34, 0x0a, 0x1e, 0x61, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x64, 0x65, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x12, 0x44, 0x65, 0x72, 0x65, 0x66, - 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x13, 0x64, - 0x65, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, - 0x65, 0x73, 0x42, 0x60, 0xa2, 0xe3, 0x29, 0x04, 0x61, 0x75, 0x74, 0x68, 0x5a, 0x56, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, - 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b, 0x2f, - 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x61, 0x75, 0x74, - 0x68, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x3b, 0x61, 0x75, 0x74, 0x68, 0x6d, 0x65, 0x74, - 0x68, 0x6f, 0x64, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x12, 0x5d, 0x0a, 0x07, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x64, 0x6e, + 0x18, 0xbe, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x24, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1c, 0x0a, + 0x12, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x62, 0x69, 0x6e, 0x64, + 0x5f, 0x64, 0x6e, 0x12, 0x06, 0x42, 0x69, 0x6e, 0x64, 0x44, 0x6e, 0x52, 0x07, 0x62, 0x69, 0x6e, + 0x64, 0x5f, 0x64, 0x6e, 0x12, 0x75, 0x0a, 0x0d, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0xc8, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x30, 0xa0, 0xda, 0x29, 0x01, + 0xc2, 0xdd, 0x29, 0x28, 0x0a, 0x18, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x2e, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x0c, + 0x42, 0x69, 0x6e, 0x64, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x0d, 0x62, 0x69, + 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x2f, 0x0a, 0x12, 0x62, + 0x69, 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, + 0x63, 0x18, 0xd2, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x70, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x12, 0x62, 0x0a, 0x10, + 0x75, 0x73, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, + 0x18, 0xdc, 0x01, 0x20, 0x01, 0x28, 0x08, 0x42, 0x35, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, + 0x2d, 0x0a, 0x1b, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x73, + 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x0e, + 0x55, 0x73, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x10, + 0x75, 0x73, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, + 0x12, 0x7a, 0x0a, 0x16, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x74, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0xe6, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x42, 0x41, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x39, 0x0a, 0x21, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x12, 0x14, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x4d, 0x61, 0x70, 0x73, 0x52, 0x16, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x12, 0x66, 0x0a, 0x11, + 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, + 0x65, 0x18, 0xf0, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x37, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, + 0x29, 0x2f, 0x0a, 0x1c, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x6d, + 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, + 0x12, 0x0f, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, + 0x65, 0x52, 0x11, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x12, 0x8d, 0x01, 0x0a, 0x13, 0x64, 0x65, 0x72, 0x65, 0x66, 0x65, 0x72, + 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0xfa, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x42, 0x3c, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x34, 0x0a, 0x1e, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x64, 0x65, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, + 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x12, 0x44, 0x65, 0x72, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, + 0x13, 0x64, 0x65, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x6c, 0x69, + 0x61, 0x73, 0x65, 0x73, 0x42, 0x60, 0xa2, 0xe3, 0x29, 0x04, 0x61, 0x75, 0x74, 0x68, 0x5a, 0x56, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, + 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x61, + 0x75, 0x74, 0x68, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x3b, 0x61, 0x75, 0x74, 0x68, 0x6d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var (