diff --git a/CHANGELOG.md b/CHANGELOG.md index f57eb60c48..4ba97466dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ Canonical reference for changes, improvements, and bugfixes for Boundary. ## Next +### New and Improved + +* OIDC Accounts: When performing a `read` on an `oidc` type account, the + original token and userinfo claims are provided in the output. This can make + it significantly easier to write filters to create [managed + groups](https://www.boundaryproject.io/docs/concepts/filtering/oidc-managed-groups). + ([PR](https://github.com/hashicorp/boundary/pull/1419)) + ### Bug Fixes * config: Fix error when populating all `kms` purposes in separate blocks (as diff --git a/api/accounts/oidc_account_attributes.gen.go b/api/accounts/oidc_account_attributes.gen.go index 6d315c374c..c1ca25c0df 100644 --- a/api/accounts/oidc_account_attributes.gen.go +++ b/api/accounts/oidc_account_attributes.gen.go @@ -2,8 +2,10 @@ package accounts type OidcAccountAttributes struct { - Issuer string `json:"issuer,omitempty"` - Subject string `json:"subject,omitempty"` - FullName string `json:"full_name,omitempty"` - Email string `json:"email,omitempty"` + Issuer string `json:"issuer,omitempty"` + Subject string `json:"subject,omitempty"` + FullName string `json:"full_name,omitempty"` + Email string `json:"email,omitempty"` + TokenClaims map[string]interface{} `json:"token_claims,omitempty"` + UserinfoClaims map[string]interface{} `json:"userinfo_claims,omitempty"` } diff --git a/internal/auth/oidc/repository_auth_method.go b/internal/auth/oidc/repository_auth_method.go index 8d9037c60d..d153cf5eed 100644 --- a/internal/auth/oidc/repository_auth_method.go +++ b/internal/auth/oidc/repository_auth_method.go @@ -2,6 +2,7 @@ package oidc import ( "context" + "encoding/json" "fmt" "net/url" "strings" @@ -75,7 +76,35 @@ func (r *Repository) upsertAccount(ctx context.Context, am *AuthMethod, IdTokenC values := []interface{}{pubId, am.PublicId, iss, sub} var conflictClauses, fieldMasks, nullMasks []string - var foundEmail, foundName interface{} + { + marshaledTokenClaims, err := json.Marshal(IdTokenClaims) + if err != nil { + return nil, errors.Wrap(err, op) + } + columns, values = append(columns, "token_claims"), append(values, string(marshaledTokenClaims)) + conflictClauses = append(conflictClauses, fmt.Sprintf("token_claims = $%d", len(values))) + fieldMasks = append(fieldMasks, TokenClaimsField) + } + { + marshaledAccessTokenClaims, err := json.Marshal(AccessTokenClaims) + if err != nil { + return nil, errors.Wrap(err, op) + } + columns, values = append(columns, "userinfo_claims"), append(values, string(marshaledAccessTokenClaims)) + conflictClauses = append(conflictClauses, fmt.Sprintf("userinfo_claims = $%d", len(values))) + fieldMasks = append(fieldMasks, UserinfoClaimsField) + } + + issAsUrl, err := url.Parse(iss) + if err != nil { + return nil, errors.New(errors.Unknown, op, "unable to parse issuer", errors.WithWrap(err)) + } + acctForOplog, err := NewAccount(am.PublicId, sub, WithIssuer(issAsUrl)) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to create new acct for oplog")) + } + + var foundName interface{} switch { case AccessTokenClaims[fromName] != nil: foundName = AccessTokenClaims[fromName] @@ -83,10 +112,17 @@ func (r *Repository) upsertAccount(ctx context.Context, am *AuthMethod, IdTokenC case IdTokenClaims[fromName] != nil: foundName = IdTokenClaims[fromName] columns, values = append(columns, "full_name"), append(values, foundName) - default: + } + if foundName != nil { + acctForOplog.FullName = foundName.(string) + conflictClauses = append(conflictClauses, fmt.Sprintf("full_name = $%d", len(values))) + fieldMasks = append(fieldMasks, NameField) + } else { conflictClauses = append(conflictClauses, "full_name = NULL") nullMasks = append(nullMasks, NameField) } + + var foundEmail interface{} switch { case AccessTokenClaims[fromEmail] != nil: foundEmail = AccessTokenClaims[fromEmail] @@ -94,36 +130,14 @@ func (r *Repository) upsertAccount(ctx context.Context, am *AuthMethod, IdTokenC case IdTokenClaims[fromEmail] != nil: foundEmail = IdTokenClaims[fromEmail] columns, values = append(columns, "email"), append(values, foundEmail) - default: - conflictClauses = append(conflictClauses, "email = NULL") - nullMasks = append(nullMasks, "Email") - } - - if foundName != nil { - values = append(values, foundName) - conflictClauses = append(conflictClauses, fmt.Sprintf("full_name = $%d", len(values))) - fieldMasks = append(fieldMasks, NameField) } if foundEmail != nil { - values = append(values, foundEmail) + acctForOplog.Email = foundEmail.(string) conflictClauses = append(conflictClauses, fmt.Sprintf("email = $%d", len(values))) fieldMasks = append(fieldMasks, "Email") - } - - issAsUrl, err := url.Parse(iss) - if err != nil { - return nil, errors.New(errors.Unknown, op, "unable to parse issuer", errors.WithWrap(err)) - } - acctForOplog, err := NewAccount(am.PublicId, sub, WithIssuer(issAsUrl)) - if err != nil { - return nil, errors.Wrap(err, op, errors.WithMsg("unable to create new acct for oplog")) - } - - if foundName != nil { - acctForOplog.FullName = foundName.(string) - } - if foundEmail != nil { - acctForOplog.Email = foundEmail.(string) + } else { + conflictClauses = append(conflictClauses, "email = NULL") + nullMasks = append(nullMasks, "Email") } placeHolders := make([]string, 0, len(columns)) diff --git a/internal/auth/oidc/repository_auth_method_test.go b/internal/auth/oidc/repository_auth_method_test.go index b9f78bc107..cf1067808b 100644 --- a/internal/auth/oidc/repository_auth_method_test.go +++ b/internal/auth/oidc/repository_auth_method_test.go @@ -61,9 +61,11 @@ func Test_upsertAccount(t *testing.T) { idClaims: map[string]interface{}{"iss": "https://alice-active-priv.com", "sub": "success-defaults"}, atClaims: map[string]interface{}{}, wantAcct: &Account{Account: &store.Account{ - AuthMethodId: amActivePriv.PublicId, - Issuer: "https://alice-active-priv.com", - Subject: "success-defaults", + AuthMethodId: amActivePriv.PublicId, + Issuer: "https://alice-active-priv.com", + Subject: "success-defaults", + TokenClaims: `{"iss":"https://alice-active-priv.com","sub":"success-defaults"}`, + UserinfoClaims: "{}", }}, }, { @@ -72,11 +74,13 @@ func Test_upsertAccount(t *testing.T) { idClaims: map[string]interface{}{"iss": "https://alice-active-priv.com", "sub": "success-atTk-full-name-and-email"}, atClaims: map[string]interface{}{"name": "alice eve-smith", "email": "alice@alice.com"}, wantAcct: &Account{Account: &store.Account{ - AuthMethodId: amActivePriv.PublicId, - Issuer: "https://alice-active-priv.com", - Subject: "success-atTk-full-name-and-email", - Email: "alice@alice.com", - FullName: "alice eve-smith", + AuthMethodId: amActivePriv.PublicId, + Issuer: "https://alice-active-priv.com", + Subject: "success-atTk-full-name-and-email", + Email: "alice@alice.com", + FullName: "alice eve-smith", + TokenClaims: `{"iss":"https://alice-active-priv.com","sub":"success-atTk-full-name-and-email"}`, + UserinfoClaims: `{"email":"alice@alice.com","name":"alice eve-smith"}`, }}, }, { @@ -85,11 +89,13 @@ func Test_upsertAccount(t *testing.T) { idClaims: map[string]interface{}{"iss": "https://alice-active-priv.com", "sub": "success-idTk-full-name-and-email", "name": "alice eve-smith", "email": "alice@alice.com"}, atClaims: map[string]interface{}{}, wantAcct: &Account{Account: &store.Account{ - AuthMethodId: amActivePriv.PublicId, - Issuer: "https://alice-active-priv.com", - Subject: "success-idTk-full-name-and-email", - Email: "alice@alice.com", - FullName: "alice eve-smith", + AuthMethodId: amActivePriv.PublicId, + Issuer: "https://alice-active-priv.com", + Subject: "success-idTk-full-name-and-email", + Email: "alice@alice.com", + FullName: "alice eve-smith", + TokenClaims: `{"email":"alice@alice.com","iss":"https://alice-active-priv.com","name":"alice eve-smith","sub":"success-idTk-full-name-and-email"}`, + UserinfoClaims: `{}`, }}, }, { @@ -98,9 +104,11 @@ func Test_upsertAccount(t *testing.T) { idClaims: map[string]interface{}{"iss": "https://alice-active-priv.com", "sub": "success-defaults", "oid": "success-map"}, atClaims: map[string]interface{}{}, wantAcct: &Account{Account: &store.Account{ - AuthMethodId: amWithMapping.PublicId, - Issuer: "https://alice-active-priv.com", - Subject: "success-map", + AuthMethodId: amWithMapping.PublicId, + Issuer: "https://alice-active-priv.com", + Subject: "success-map", + TokenClaims: `{"iss":"https://alice-active-priv.com","oid":"success-map","sub":"success-defaults"}`, + UserinfoClaims: `{}`, }}, }, { diff --git a/internal/auth/oidc/repository_auth_method_update.go b/internal/auth/oidc/repository_auth_method_update.go index 87c846d361..477b53aa80 100644 --- a/internal/auth/oidc/repository_auth_method_update.go +++ b/internal/auth/oidc/repository_auth_method_update.go @@ -34,6 +34,8 @@ const ( CertificatesField = "Certificates" ClaimsScopesField = "ClaimsScopes" AccountClaimMapsField = "AccountClaimMaps" + TokenClaimsField = "TokenClaims" + UserinfoClaimsField = "UserinfoClaims" ) // UpdateAuthMethod will retrieve the auth method from the repository, @@ -654,7 +656,7 @@ func applyUpdate(new, orig *AuthMethod, fieldMaskPaths []string) *AuthMethod { // (and associated data) are validated with the retrieved document. The issuer and // id token signing algorithm in the configuration are validated with the // retrieved document. ValidateDiscoveryInfo also verifies the authorization, token, -// and user_info endpoints by connecting to each and uses any certificates in the +// and userinfo endpoints by connecting to each and uses any certificates in the // configuration as trust anchors to confirm connectivity. // // Options supported are: WithPublicId, WithAuthMethod diff --git a/internal/auth/oidc/store/oidc.pb.go b/internal/auth/oidc/store/oidc.pb.go index 2e98b80bf7..c3e7beff0e 100644 --- a/internal/auth/oidc/store/oidc.pb.go +++ b/internal/auth/oidc/store/oidc.pb.go @@ -356,6 +356,12 @@ type Account struct { // email is a string that maps to the OIDC email claim. // @inject_tag: `gorm:"default:null"` Email string `protobuf:"bytes,110,opt,name=email,proto3" json:"email,omitempty" gorm:"default:null"` + // token_claims are the marshaled claims from the token. + // @inject_tag: `gorm:"default:null"` + TokenClaims string `protobuf:"bytes,120,opt,name=token_claims,json=tokenClaims,proto3" json:"token_claims,omitempty" gorm:"default:null"` + // userinfo_claims are the marshaled claims from userinfo. + // @inject_tag: `gorm:"default:null"` + UserinfoClaims string `protobuf:"bytes,130,opt,name=userinfo_claims,json=userinfoClaims,proto3" json:"userinfo_claims,omitempty" gorm:"default:null"` } func (x *Account) Reset() { @@ -467,6 +473,20 @@ func (x *Account) GetEmail() string { return "" } +func (x *Account) GetTokenClaims() string { + if x != nil { + return x.TokenClaims + } + return "" +} + +func (x *Account) GetUserinfoClaims() string { + if x != nil { + return x.UserinfoClaims + } + return "" +} + // SigningAlg entries are the signing algorithms allowed for an oidc auth method. type SigningAlg struct { state protoimpl.MessageState @@ -1120,7 +1140,7 @@ 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, 0xcd, 0x03, 0x0a, + 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, @@ -1149,99 +1169,104 @@ var file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDesc = []byte{ 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, 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, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 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, + 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, + 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, 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, 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, + 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, 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, 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, + 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, 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 ( diff --git a/internal/cmd/base/format.go b/internal/cmd/base/format.go index bf33f3b3dd..55ff54b9db 100644 --- a/internal/cmd/base/format.go +++ b/internal/cmd/base/format.go @@ -127,11 +127,26 @@ func WrapMap(prefixSpaces, maxLengthOverride int, input map[string]interface{}) if spaces < 0 { spaces = 0 } + vOut := fmt.Sprintf("%v", v) + switch v.(type) { + case map[string]interface{}: + buf, err := json.MarshalIndent(v, strings.Repeat(" ", prefixSpaces), " ") + if err != nil { + vOut = "[Unable to Print]" + break + } + bStrings := strings.Split(string(buf), "\n") + if len(bStrings) > 0 { + // Indent doesn't apply to the first line 🙄 + bStrings[0] = fmt.Sprintf("\n%s%s", strings.Repeat(" ", prefixSpaces), bStrings[0]) + } + vOut = strings.Join(bStrings, "\n") + } ret = append(ret, fmt.Sprintf("%s%s%s%s", strings.Repeat(" ", prefixSpaces), fmt.Sprintf("%s: ", k), strings.Repeat(" ", spaces), - fmt.Sprintf("%v", v), + vOut, )) } diff --git a/internal/db/schema/migrations/postgres/13/01_oidc_add_claims.up.sql b/internal/db/schema/migrations/postgres/13/01_oidc_add_claims.up.sql new file mode 100644 index 0000000000..45ae8effca --- /dev/null +++ b/internal/db/schema/migrations/postgres/13/01_oidc_add_claims.up.sql @@ -0,0 +1,16 @@ +begin; + +alter table auth_oidc_account + add column token_claims text + constraint token_claims_must_not_be_empty + check( + length(trim(token_claims)) > 0 + ); +alter table auth_oidc_account + add column userinfo_claims text + constraint userinfo_claims_must_not_be_empty + check( + length(trim(userinfo_claims)) > 0 + ); + +commit; \ No newline at end of file diff --git a/internal/db/schema/postgres_migration.gen.go b/internal/db/schema/postgres_migration.gen.go index b838438c88..f35d29c0e5 100644 --- a/internal/db/schema/postgres_migration.gen.go +++ b/internal/db/schema/postgres_migration.gen.go @@ -4,7 +4,7 @@ package schema func init() { migrationStates["postgres"] = migrationState{ - binarySchemaVersion: 12001, + binarySchemaVersion: 13001, upMigrations: map[int][]byte{ 1: []byte(` create domain wt_public_id as text @@ -6121,6 +6121,20 @@ create function wt_sub_seconds(sec integer, ts timestamp with time zone) returns null on null input; comment on function wt_add_seconds_to_now is 'wt_sub_seconds_from_now returns current_timestamp - sec.'; +`), + 13001: []byte(` +alter table auth_oidc_account + add column token_claims text + constraint token_claims_must_not_be_empty + check( + length(trim(token_claims)) > 0 + ); +alter table auth_oidc_account + add column userinfo_claims text + constraint userinfo_claims_must_not_be_empty + check( + length(trim(userinfo_claims)) > 0 + ); `), 2001: []byte(` -- log_migration entries represent logs generated during migrations diff --git a/internal/gen/controller/api/resources/accounts/account.pb.go b/internal/gen/controller/api/resources/accounts/account.pb.go index 7f23c0d55c..deea0c162d 100644 --- a/internal/gen/controller/api/resources/accounts/account.pb.go +++ b/internal/gen/controller/api/resources/accounts/account.pb.go @@ -248,6 +248,10 @@ type OidcAccountAttributes struct { FullName string `protobuf:"bytes,100,opt,name=full_name,proto3" json:"full_name,omitempty"` // Output only. email is a string that maps to the OIDC email claim. Email string `protobuf:"bytes,110,opt,name=email,proto3" json:"email,omitempty"` + // Output only. token_claims are the marshaled claims from the token. + TokenClaims *structpb.Struct `protobuf:"bytes,120,opt,name=token_claims,json=tokenClaims,proto3" json:"token_claims,omitempty"` + // Output only. userinfo_claims are the marshaled claims from userinfo. + UserinfoClaims *structpb.Struct `protobuf:"bytes,130,opt,name=userinfo_claims,json=userinfoClaims,proto3" json:"userinfo_claims,omitempty"` } func (x *OidcAccountAttributes) Reset() { @@ -310,6 +314,20 @@ func (x *OidcAccountAttributes) GetEmail() string { return "" } +func (x *OidcAccountAttributes) GetTokenClaims() *structpb.Struct { + if x != nil { + return x.TokenClaims + } + return nil +} + +func (x *OidcAccountAttributes) GetUserinfoClaims() *structpb.Struct { + if x != nil { + return x.UserinfoClaims + } + return nil +} + var File_controller_api_resources_accounts_v1_account_proto protoreflect.FileDescriptor var file_controller_api_resources_accounts_v1_account_proto_rawDesc = []byte{ @@ -381,7 +399,7 @@ var file_controller_api_resources_accounts_v1_account_proto_rawDesc = []byte{ 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, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, - 0x22, 0x89, 0x01, 0x0a, 0x15, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x22, 0x88, 0x02, 0x0a, 0x15, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x18, 0x50, 0x20, 0x01, 0x28, 0x09, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, @@ -389,13 +407,21 @@ var file_controller_api_resources_accounts_v1_account_proto_rawDesc = []byte{ 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6e, 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, 0x42, 0x57, 0x5a, 0x55, - 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, 0x67, 0x65, 0x6e, 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, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x3b, 0x61, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x3a, 0x0a, 0x0c, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x78, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0b, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x75, 0x73, 0x65, 0x72, + 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x82, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0e, 0x75, 0x73, 0x65, + 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x42, 0x57, 0x5a, 0x55, 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, 0x67, 0x65, 0x6e, 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, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x3b, 0x61, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -428,11 +454,13 @@ var file_controller_api_resources_accounts_v1_account_proto_depIdxs = []int32{ 5, // 4: controller.api.resources.accounts.v1.Account.updated_time:type_name -> google.protobuf.Timestamp 6, // 5: controller.api.resources.accounts.v1.Account.attributes:type_name -> google.protobuf.Struct 4, // 6: controller.api.resources.accounts.v1.PasswordAccountAttributes.password:type_name -> google.protobuf.StringValue - 7, // [7:7] is the sub-list for method output_type - 7, // [7:7] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 6, // 7: controller.api.resources.accounts.v1.OidcAccountAttributes.token_claims:type_name -> google.protobuf.Struct + 6, // 8: controller.api.resources.accounts.v1.OidcAccountAttributes.userinfo_claims:type_name -> google.protobuf.Struct + 9, // [9:9] is the sub-list for method output_type + 9, // [9:9] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name } func init() { file_controller_api_resources_accounts_v1_account_proto_init() } diff --git a/internal/gen/controller/api/services/managed_group_service.pb.go b/internal/gen/controller/api/services/managed_group_service.pb.go index 0b3b3566ea..04fb34dbcf 100644 --- a/internal/gen/controller/api/services/managed_group_service.pb.go +++ b/internal/gen/controller/api/services/managed_group_service.pb.go @@ -13,7 +13,6 @@ import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" - _ "google.golang.org/protobuf/types/known/wrapperspb" reflect "reflect" sync "sync" ) @@ -533,8 +532,6 @@ var file_controller_api_services_v1_managed_group_service_proto_rawDesc = []byte 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x3d, 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, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2f, 0x76, 0x31, 0x2f, diff --git a/internal/proto/local/controller/api/resources/accounts/v1/account.proto b/internal/proto/local/controller/api/resources/accounts/v1/account.proto index f4c16f564e..390fdd509b 100644 --- a/internal/proto/local/controller/api/resources/accounts/v1/account.proto +++ b/internal/proto/local/controller/api/resources/accounts/v1/account.proto @@ -74,4 +74,10 @@ message OidcAccountAttributes { // Output only. email is a string that maps to the OIDC email claim. string email = 110; + + // Output only. token_claims are the marshaled claims from the token. + google.protobuf.Struct token_claims = 120; + + // Output only. userinfo_claims are the marshaled claims from userinfo. + google.protobuf.Struct userinfo_claims = 130; } diff --git a/internal/proto/local/controller/api/services/v1/managed_group_service.proto b/internal/proto/local/controller/api/services/v1/managed_group_service.proto index 4804b1d683..f06f2a31a0 100644 --- a/internal/proto/local/controller/api/services/v1/managed_group_service.proto +++ b/internal/proto/local/controller/api/services/v1/managed_group_service.proto @@ -7,7 +7,6 @@ option go_package = "github.com/hashicorp/boundary/internal/gen/controller/api/s import "protoc-gen-openapiv2/options/annotations.proto"; import "google/api/annotations.proto"; import "google/protobuf/field_mask.proto"; -import "google/protobuf/wrappers.proto"; import "controller/api/resources/managedgroups/v1/managed_group.proto"; service ManagedGroupService { diff --git a/internal/proto/local/controller/storage/auth/oidc/store/v1/oidc.proto b/internal/proto/local/controller/storage/auth/oidc/store/v1/oidc.proto index 33d82aa341..20de4ea367 100644 --- a/internal/proto/local/controller/storage/auth/oidc/store/v1/oidc.proto +++ b/internal/proto/local/controller/storage/auth/oidc/store/v1/oidc.proto @@ -170,6 +170,14 @@ message Account { // email is a string that maps to the OIDC email claim. // @inject_tag: `gorm:"default:null"` string email = 110; + + // token_claims are the marshaled claims from the token. + // @inject_tag: `gorm:"default:null"` + string token_claims = 120; + + // userinfo_claims are the marshaled claims from userinfo. + // @inject_tag: `gorm:"default:null"` + string userinfo_claims = 130; } // SigningAlg entries are the signing algorithms allowed for an oidc auth method. diff --git a/internal/servers/controller/handlers/accounts/account_service.go b/internal/servers/controller/handlers/accounts/account_service.go index aacc079739..da91d6c3e3 100644 --- a/internal/servers/controller/handlers/accounts/account_service.go +++ b/internal/servers/controller/handlers/accounts/account_service.go @@ -2,6 +2,7 @@ package accounts import ( "context" + "encoding/json" "fmt" "net/url" "strings" @@ -23,6 +24,7 @@ import ( "github.com/hashicorp/boundary/internal/types/action" "github.com/hashicorp/boundary/internal/types/resource" "google.golang.org/grpc/codes" + "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -816,6 +818,8 @@ func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Ty } func toProto(ctx context.Context, in auth.Account, opt ...handlers.Option) (*pb.Account, error) { + const op = "accounts.(Service).toProto" + opts := handlers.GetOpts(opt...) if opts.WithOutputFields == nil { return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building account proto") @@ -879,6 +883,26 @@ func toProto(ctx context.Context, in auth.Account, opt ...handlers.Option) (*pb. FullName: i.GetFullName(), Email: i.GetEmail(), } + if s := i.GetTokenClaims(); s != "" { + m := make(map[string]interface{}) + var err error + if err = json.Unmarshal([]byte(s), &m); err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("error unmarshaling stored token claims")) + } + if attrs.TokenClaims, err = structpb.NewStruct(m); err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("error converting stored token claims to protobuf struct")) + } + } + if s := i.GetUserinfoClaims(); s != "" { + m := make(map[string]interface{}) + var err error + if err = json.Unmarshal([]byte(s), &m); err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("error unmarshaling stored userinfo claims")) + } + if attrs.UserinfoClaims, err = structpb.NewStruct(m); err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("error converting stored userinfo claims to protobuf struct")) + } + } st, err := handlers.ProtoToStruct(attrs) if err != nil { return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "failed building oidc attribute struct: %v", err) diff --git a/internal/session/repository_connection.go b/internal/session/repository_connection.go index f6f103f1e5..cfd7b957bd 100644 --- a/internal/session/repository_connection.go +++ b/internal/session/repository_connection.go @@ -196,7 +196,6 @@ func (r *Repository) CloseConnectionsForDeadWorkers(ctx context.Context, gracePe return nil }, ) - if err != nil { return nil, errors.Wrap(err, op) }