diff --git a/internal/mcp/handler_authorization.go b/internal/mcp/handler_authorization.go index efa7c33b5..072a37837 100644 --- a/internal/mcp/handler_authorization.go +++ b/internal/mcp/handler_authorization.go @@ -74,8 +74,8 @@ func (srv *Handler) Authorize(w http.ResponseWriter, r *http.Request) { return } - if err := oauth21.ValidateAuthorizationRequest(client, v); err != nil { - log.Ctx(ctx).Error().Err(err).Msg("failed to validate authorization request") + if err := oauth21.ValidateAuthorizationRequest(client.ResponseMetadata, v); err != nil { + log.Ctx(ctx).Error().Err(err).Msg("failed to validate authorization request for a client") ve := oauth21.Error{Code: oauth21.InvalidRequest} _ = errors.As(err, &ve) oauth21.ErrorResponse(w, http.StatusBadRequest, ve.Code) diff --git a/internal/mcp/handler_metadata.go b/internal/mcp/handler_metadata.go index 338782d7a..9ac614fed 100644 --- a/internal/mcp/handler_metadata.go +++ b/internal/mcp/handler_metadata.go @@ -119,7 +119,7 @@ func getAuthorizationServerMetadata(host, prefix string) AuthorizationServerMeta ResponseTypesSupported: []string{"code"}, CodeChallengeMethodsSupported: []string{"S256"}, TokenEndpoint: P(path.Join(prefix, tokenEndpoint)), - TokenEndpointAuthMethodsSupported: []string{"none"}, + TokenEndpointAuthMethodsSupported: []string{"client_secret_basic", "none"}, GrantTypesSupported: []string{"authorization_code", "refresh_token"}, RevocationEndpoint: P(path.Join(prefix, revocationEndpoint)), RevocationEndpointAuthMethodsSupported: []string{"client_secret_post"}, diff --git a/internal/mcp/handler_register_client.go b/internal/mcp/handler_register_client.go index 5b7c38912..9fa5b875a 100644 --- a/internal/mcp/handler_register_client.go +++ b/internal/mcp/handler_register_client.go @@ -2,15 +2,16 @@ package mcp import ( "encoding/json" + "fmt" "io" "net/http" - "time" - "github.com/bufbuild/protovalidate-go" - "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/pomerium/pomerium/internal/log" rfc7591v1 "github.com/pomerium/pomerium/internal/rfc7591" + "github.com/pomerium/pomerium/pkg/cryptutil" ) const maxClientRegistrationPayload = 1024 * 1024 // 1MB @@ -34,44 +35,24 @@ func (srv *Handler) RegisterClient(w http.ResponseWriter, r *http.Request) { return } - v := new(rfc7591v1.ClientMetadata) - err = protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(data, v) + clientRegistration, err := createClientRegistrationFromMetadata(data) if err != nil { - log.Ctx(ctx).Error().Err(err).Msg("failed to unmarshal request body") - http.Error(w, "failed to unmarshal request body", http.StatusBadRequest) - return - } - - err = protovalidate.Validate(v) - if err != nil { - log.Ctx(ctx).Error().Err(err).Msg("failed to validate request body") + log.Ctx(ctx).Error(). + Str("request", string(data)). + Err(err).Msg("create client registration") clientRegistrationBadRequest(w, err) return } - id, err := srv.storage.RegisterClient(ctx, v) + id, err := srv.storage.RegisterClient(ctx, clientRegistration) if err != nil { log.Ctx(ctx).Error().Err(err).Msg("failed to register client") http.Error(w, "failed to register client", http.StatusInternalServerError) } - resp := struct { - *rfc7591v1.ClientMetadata - ClientID string `json:"client_id"` - ClientIDIssuedAt int64 `json:"client_id_issued_at"` - }{ - ClientMetadata: v, - ClientID: id, - ClientIDIssuedAt: time.Now().Unix(), - } - data, err = json.Marshal(resp) - if err != nil { - log.Ctx(ctx).Error().Err(err).Msg("failed to marshal response") - http.Error(w, "failed to marshal response", http.StatusInternalServerError) - return - } w.Header().Set("Content-Type", "application/json") - _, err = w.Write(data) + err = rfc7591v1.WriteRegistrationResponse(w, id, + clientRegistration.ClientSecret, clientRegistration.ResponseMetadata) if err != nil { log.Ctx(ctx).Error().Err(err).Msg("failed to write response") return @@ -93,3 +74,34 @@ func clientRegistrationBadRequest(w http.ResponseWriter, err error) { w.WriteHeader(http.StatusBadRequest) _, _ = w.Write(data) } + +func createClientRegistrationFromMetadata( + requestMetadataText []byte, +) (*rfc7591v1.ClientRegistration, error) { + requestMetadata, err := rfc7591v1.ParseMetadata(requestMetadataText) + if err != nil { + return nil, fmt.Errorf("parse: %w", err) + } + + err = requestMetadata.Validate() + if err != nil { + return nil, fmt.Errorf("validate: %w", err) + } + + responseMetadata := proto.CloneOf(requestMetadata) + responseMetadata.SetDefaults() + + registration := &rfc7591v1.ClientRegistration{ + RequestMetadata: requestMetadata, + ResponseMetadata: responseMetadata, + } + + if requestMetadata.GetTokenEndpointAuthMethod() != rfc7591v1.TokenEndpointAuthMethodNone { + registration.ClientSecret = &rfc7591v1.ClientSecret{ + Value: cryptutil.NewRandomStringN(32), + CreatedAt: timestamppb.Now(), + } + } + + return registration, nil +} diff --git a/internal/mcp/handler_token.go b/internal/mcp/handler_token.go index d9618ff79..63c9cb5ab 100644 --- a/internal/mcp/handler_token.go +++ b/internal/mcp/handler_token.go @@ -2,6 +2,7 @@ package mcp import ( "encoding/json" + "fmt" "net/http" "time" @@ -12,6 +13,7 @@ import ( "github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/internal/oauth21" oauth21proto "github.com/pomerium/pomerium/internal/oauth21/gen" + rfc7591v1 "github.com/pomerium/pomerium/internal/rfc7591" ) // Token handles the /token endpoint. @@ -21,54 +23,65 @@ func (srv *Handler) Token(w http.ResponseWriter, r *http.Request) { return } - req, err := oauth21.ParseTokenRequest(r) + ctx := r.Context() + req, err := srv.getTokenRequest(r) if err != nil { - log.Ctx(r.Context()).Error().Err(err).Msg("failed to parse token request") + log.Ctx(ctx).Error().Err(err).Msg("get token request failed") oauth21.ErrorResponse(w, http.StatusBadRequest, oauth21.InvalidRequest) return } switch req.GrantType { case "authorization_code": + log.Ctx(ctx).Debug().Msg("handling authorization_code token request") srv.handleAuthorizationCodeToken(w, r, req) default: + log.Ctx(ctx).Error().Msgf("unsupported grant type: %s", req.GrantType) oauth21.ErrorResponse(w, http.StatusBadRequest, oauth21.UnsupportedGrantType) return } } -func (srv *Handler) handleAuthorizationCodeToken(w http.ResponseWriter, r *http.Request, req *oauth21proto.TokenRequest) { +func (srv *Handler) handleAuthorizationCodeToken(w http.ResponseWriter, r *http.Request, tokenReq *oauth21proto.TokenRequest) { ctx := r.Context() - if req.ClientId == nil { + if tokenReq.ClientId == nil { + log.Ctx(ctx).Error().Msg("missing client_id in token request") oauth21.ErrorResponse(w, http.StatusBadRequest, oauth21.InvalidClient) return } - if req.Code == nil { + if tokenReq.Code == nil { + log.Ctx(ctx).Error().Msg("missing code in token request") oauth21.ErrorResponse(w, http.StatusBadRequest, oauth21.InvalidGrant) return } - code, err := DecryptCode(CodeTypeAuthorization, *req.Code, srv.cipher, *req.ClientId, time.Now()) + code, err := DecryptCode(CodeTypeAuthorization, *tokenReq.Code, srv.cipher, *tokenReq.ClientId, time.Now()) if err != nil { + log.Ctx(ctx).Error().Err(err).Msg("failed to decrypt authorization code") oauth21.ErrorResponse(w, http.StatusBadRequest, oauth21.InvalidGrant) return } authReq, err := srv.storage.GetAuthorizationRequest(ctx, code.Id) if status.Code(err) == codes.NotFound { + log.Ctx(ctx).Error().Msg("authorization request not found") oauth21.ErrorResponse(w, http.StatusBadRequest, oauth21.InvalidGrant) return } if err != nil { + log.Ctx(ctx).Error().Err(err).Msg("failed to get authorization request and client") http.Error(w, "internal error", http.StatusInternalServerError) } - if *req.ClientId != authReq.ClientId { + if *tokenReq.ClientId != authReq.ClientId { + log.Ctx(ctx).Error().Msgf("client ID mismatch: %s != %s", *tokenReq.ClientId, authReq.ClientId) oauth21.ErrorResponse(w, http.StatusBadRequest, oauth21.InvalidGrant) + return } - err = CheckPKCE(authReq.GetCodeChallengeMethod(), authReq.GetCodeChallenge(), req.GetCodeVerifier()) + err = CheckPKCE(authReq.GetCodeChallengeMethod(), authReq.GetCodeChallenge(), tokenReq.GetCodeVerifier()) if err != nil { + log.Ctx(ctx).Error().Err(err).Msg("failed to check PKCE") oauth21.ErrorResponse(w, http.StatusBadRequest, oauth21.InvalidGrant) return } @@ -77,24 +90,28 @@ func (srv *Handler) handleAuthorizationCodeToken(w http.ResponseWriter, r *http. // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-12#section-4.1.3 err = srv.storage.DeleteAuthorizationRequest(ctx, code.Id) if err != nil { + log.Ctx(ctx).Error().Err(err).Msg("failed to delete authorization request") http.Error(w, "internal error", http.StatusInternalServerError) return } session, err := srv.storage.GetSession(ctx, authReq.SessionId) if status.Code(err) == codes.NotFound { + log.Ctx(ctx).Error().Msg("session not found") oauth21.ErrorResponse(w, http.StatusBadRequest, oauth21.InvalidGrant) return } accessToken, err := srv.GetAccessTokenForSession(session.Id, session.ExpiresAt.AsTime()) if err != nil { + log.Ctx(ctx).Error().Err(err).Msg("failed to get access token for session") http.Error(w, "internal error", http.StatusInternalServerError) return } expiresIn := time.Until(session.ExpiresAt.AsTime()) if expiresIn < 0 { + log.Ctx(ctx).Error().Msg("session has already expired") oauth21.ErrorResponse(w, http.StatusBadRequest, oauth21.InvalidGrant) return } @@ -107,6 +124,7 @@ func (srv *Handler) handleAuthorizationCodeToken(w http.ResponseWriter, r *http. data, err := json.Marshal(resp) // not using protojson.Marshal here because it emits numbers as strings, which is valid, but for some reason Node.js / mcp typescript SDK doesn't like it if err != nil { + log.Ctx(ctx).Error().Err(err).Msg("failed to marshal token response") http.Error(w, "internal error", http.StatusInternalServerError) return } @@ -116,3 +134,46 @@ func (srv *Handler) handleAuthorizationCodeToken(w http.ResponseWriter, r *http. w.WriteHeader(http.StatusOK) _, _ = w.Write(data) } + +func (srv *Handler) getTokenRequest( + r *http.Request, +) (*oauth21proto.TokenRequest, error) { + tokenReq, err := oauth21.ParseTokenRequest(r) + if err != nil { + return nil, fmt.Errorf("failed to parse token request: %w", err) + } + + ctx := r.Context() + clientReg, err := srv.storage.GetClient(ctx, tokenReq.GetClientId()) + if err != nil { + return nil, fmt.Errorf("failed to get client registration: %w", err) + } + + m := clientReg.ResponseMetadata.GetTokenEndpointAuthMethod() + if m == rfc7591v1.TokenEndpointAuthMethodNone { + return tokenReq, nil + } + + secret := clientReg.ClientSecret + if secret == nil { + return nil, fmt.Errorf("client registration does not have a client secret") + } + if expires := secret.ExpiresAt; expires != nil && expires.AsTime().Before(time.Now()) { + return nil, fmt.Errorf("client registration client secret has expired") + } + + switch m { + case rfc7591v1.TokenEndpointAuthMethodClientSecretBasic: + case rfc7591v1.TokenEndpointAuthMethodClientSecretPost: + if tokenReq.ClientSecret == nil { + return nil, fmt.Errorf("client_secret was not provided") + } + if tokenReq.GetClientSecret() != secret.Value { + return nil, fmt.Errorf("client secret mismatch") + } + default: + return nil, fmt.Errorf("unsupported token endpoint authentication method: %s", m) + } + + return tokenReq, nil +} diff --git a/internal/mcp/storage.go b/internal/mcp/storage.go index 675818786..86f67309e 100644 --- a/internal/mcp/storage.go +++ b/internal/mcp/storage.go @@ -31,7 +31,7 @@ func NewStorage( func (storage *Storage) RegisterClient( ctx context.Context, - req *rfc7591v1.ClientMetadata, + req *rfc7591v1.ClientRegistration, ) (string, error) { data := protoutil.NewAny(req) id := uuid.NewString() @@ -51,8 +51,8 @@ func (storage *Storage) RegisterClient( func (storage *Storage) GetClient( ctx context.Context, id string, -) (*rfc7591v1.ClientMetadata, error) { - v := new(rfc7591v1.ClientMetadata) +) (*rfc7591v1.ClientRegistration, error) { + v := new(rfc7591v1.ClientRegistration) rec, err := storage.client.Get(ctx, &databroker.GetRequest{ Type: protoutil.GetTypeURL(v), Id: id, diff --git a/internal/mcp/storage_test.go b/internal/mcp/storage_test.go index 929ab184e..9b23ec1da 100644 --- a/internal/mcp/storage_test.go +++ b/internal/mcp/storage_test.go @@ -62,7 +62,7 @@ func TestStorage(t *testing.T) { t.Run("client registration", func(t *testing.T) { t.Parallel() - id, err := storage.RegisterClient(ctx, &rfc7591v1.ClientMetadata{}) + id, err := storage.RegisterClient(ctx, &rfc7591v1.ClientRegistration{}) require.NoError(t, err) require.NotEmpty(t, id) diff --git a/internal/oauth21/authorize.go b/internal/oauth21/authorize.go index 3beb781b5..198f6c60d 100644 --- a/internal/oauth21/authorize.go +++ b/internal/oauth21/authorize.go @@ -20,7 +20,7 @@ func ParseCodeGrantAuthorizeRequest(r *http.Request) (*gen.AuthorizationRequest, RedirectUri: optionalFormParam(r, "redirect_uri"), ResponseType: r.Form.Get("response_type"), State: optionalFormParam(r, "state"), - CodeChallenge: r.Form.Get("code_challenge"), + CodeChallenge: optionalFormParam(r, "code_challenge"), CodeChallengeMethod: optionalFormParam(r, "code_challenge_method"), } diff --git a/internal/oauth21/buf.lock b/internal/oauth21/buf.lock index ee39cd62d..f49df773a 100644 --- a/internal/oauth21/buf.lock +++ b/internal/oauth21/buf.lock @@ -2,5 +2,5 @@ version: v2 deps: - name: buf.build/bufbuild/protovalidate - commit: 8976f5be98c146529b1cc15cd2012b60 - digest: b5:5d513af91a439d9e78cacac0c9455c7cb885a8737d30405d0b91974fe05276d19c07a876a51a107213a3d01b83ecc912996cdad4cddf7231f91379079cf7488d + commit: b52ab10f44684cb19d1fbcad56aedd36 + digest: b5:5f464399f5ea7546eb3c6a4e822a306da538298f3d87e9f974f4523d4955de396eb2b8b52a2f3f06ac290764d80cecbaa0a4c96560558e43d9b1c722e61a9d5c diff --git a/internal/oauth21/gen/authorization_request.pb.go b/internal/oauth21/gen/authorization_request.pb.go index d1af9ebb6..580005b93 100644 --- a/internal/oauth21/gen/authorization_request.pb.go +++ b/internal/oauth21/gen/authorization_request.pb.go @@ -43,8 +43,9 @@ type AuthorizationRequest struct { State *string `protobuf:"bytes,4,opt,name=state,proto3,oneof" json:"state,omitempty"` // OPTIONAL. The scope of the access request as described by Section 1.4.1. Scopes []string `protobuf:"bytes,5,rep,name=scopes,proto3" json:"scopes,omitempty"` - // REQUIRED, assumes https://www.rfc-editor.org/rfc/rfc7636.html#section-4.1 - CodeChallenge string `protobuf:"bytes,6,opt,name=code_challenge,json=codeChallenge,proto3" json:"code_challenge,omitempty"` + // REQUIRED or RECOMMENDED, assumes https://www.rfc-editor.org/rfc/rfc7636.html#section-4.1 + // subject to whether the client is public or confidential. + CodeChallenge *string `protobuf:"bytes,6,opt,name=code_challenge,json=codeChallenge,proto3,oneof" json:"code_challenge,omitempty"` // OPTIONAL, defaults to plain if not present in the request. Code verifier // transformation method is S256 or plain. CodeChallengeMethod *string `protobuf:"bytes,7,opt,name=code_challenge_method,json=codeChallengeMethod,proto3,oneof" json:"code_challenge_method,omitempty"` @@ -124,8 +125,8 @@ func (x *AuthorizationRequest) GetScopes() []string { } func (x *AuthorizationRequest) GetCodeChallenge() string { - if x != nil { - return x.CodeChallenge + if x != nil && x.CodeChallenge != nil { + return *x.CodeChallenge } return "" } @@ -158,7 +159,7 @@ var file_authorization_request_proto_rawDesc = string([]byte{ 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x31, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0xcb, 0x03, 0x0a, 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x6f, 0x74, 0x6f, 0x22, 0xe0, 0x03, 0x0a, 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, @@ -171,33 +172,34 @@ var file_authorization_request_proto_rawDesc = string([]byte{ 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x88, 0x01, 0x01, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x6f, 0x70, 0x65, - 0x73, 0x12, 0x34, 0x0a, 0x0e, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, - 0x6e, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xba, 0x48, 0x0a, 0xc8, 0x01, - 0x01, 0x72, 0x05, 0x10, 0x2b, 0x18, 0x80, 0x01, 0x52, 0x0d, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x68, - 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x12, 0x4b, 0x0a, 0x15, 0x63, 0x6f, 0x64, 0x65, 0x5f, - 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x12, 0xba, 0x48, 0x0f, 0x72, 0x0d, 0x52, 0x04, 0x53, - 0x32, 0x35, 0x36, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x13, 0x63, 0x6f, - 0x64, 0x65, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, - 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x07, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x42, 0x06, 0xba, 0x48, - 0x03, 0xc8, 0x01, 0x01, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x42, 0x0f, 0x0a, 0x0d, - 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x42, 0x08, 0x0a, - 0x06, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x42, 0x97, 0x01, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x2e, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x32, - 0x31, 0x42, 0x19, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x31, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, - 0x69, 0x75, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x31, 0x2f, 0x67, 0x65, - 0x6e, 0xa2, 0x02, 0x03, 0x4f, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, - 0x31, 0xca, 0x02, 0x07, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x31, 0xe2, 0x02, 0x13, 0x4f, 0x61, - 0x75, 0x74, 0x68, 0x32, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0xea, 0x02, 0x07, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x73, 0x12, 0x36, 0x0a, 0x0e, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, + 0x6e, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0x72, 0x05, + 0x10, 0x2b, 0x18, 0x80, 0x01, 0x48, 0x02, 0x52, 0x0d, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x68, 0x61, + 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x88, 0x01, 0x01, 0x12, 0x4b, 0x0a, 0x15, 0x63, 0x6f, 0x64, + 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x12, 0xba, 0x48, 0x0f, 0x72, 0x0d, 0x52, + 0x04, 0x53, 0x32, 0x35, 0x36, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x48, 0x03, 0x52, 0x13, + 0x63, 0x6f, 0x64, 0x65, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x4d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, + 0x01, 0x01, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, + 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x42, 0x06, + 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x42, 0x0f, + 0x0a, 0x0d, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x42, + 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x42, 0x18, 0x0a, 0x16, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x5f, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x42, 0x97, 0x01, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x2e, 0x6f, + 0x61, 0x75, 0x74, 0x68, 0x32, 0x31, 0x42, 0x19, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, + 0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6f, 0x61, 0x75, 0x74, 0x68, + 0x32, 0x31, 0x2f, 0x67, 0x65, 0x6e, 0xa2, 0x02, 0x03, 0x4f, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x4f, + 0x61, 0x75, 0x74, 0x68, 0x32, 0x31, 0xca, 0x02, 0x07, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x31, + 0xe2, 0x02, 0x13, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x07, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x31, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( diff --git a/internal/oauth21/proto/authorization_request.proto b/internal/oauth21/proto/authorization_request.proto index a76e54d2e..20620ded1 100644 --- a/internal/oauth21/proto/authorization_request.proto +++ b/internal/oauth21/proto/authorization_request.proto @@ -34,10 +34,10 @@ message AuthorizationRequest { // OPTIONAL. The scope of the access request as described by Section 1.4.1. repeated string scopes = 5; - // REQUIRED, assumes https://www.rfc-editor.org/rfc/rfc7636.html#section-4.1 - string code_challenge = 6 [ - (buf.validate.field).required = true, - (buf.validate.field).string = {min_len: 43, max_len: 128} + // REQUIRED or RECOMMENDED, assumes https://www.rfc-editor.org/rfc/rfc7636.html#section-4.1 + // subject to whether the client is public or confidential. + optional string code_challenge = 6 [ + (buf.validate.field).string = {min_len: 43, max_len: 128} ]; // OPTIONAL, defaults to plain if not present in the request. Code verifier diff --git a/internal/oauth21/token.go b/internal/oauth21/token.go index 00319e9e2..006e95c76 100644 --- a/internal/oauth21/token.go +++ b/internal/oauth21/token.go @@ -15,6 +15,9 @@ func ParseTokenRequest(r *http.Request) (*gen.TokenRequest, error) { return nil, fmt.Errorf("failed to parse form: %w", err) } + // extract client credentials from HTTP Basic Authorization header, if present + basicID, basicSecret, basicOK := r.BasicAuth() + v := &gen.TokenRequest{ GrantType: r.Form.Get("grant_type"), Code: optionalFormParam(r, "code"), @@ -25,6 +28,15 @@ func ParseTokenRequest(r *http.Request) (*gen.TokenRequest, error) { ClientSecret: optionalFormParam(r, "client_secret"), } + if basicOK { + if v.ClientId == nil && basicID != "" { + v.ClientId = &basicID + } + if v.ClientSecret == nil && basicSecret != "" { + v.ClientSecret = &basicSecret + } + } + err = protovalidate.Validate(v) if err != nil { return nil, fmt.Errorf("failed to validate token request: %w", err) diff --git a/internal/oauth21/token_test.go b/internal/oauth21/token_test.go new file mode 100644 index 000000000..064a0cf4d --- /dev/null +++ b/internal/oauth21/token_test.go @@ -0,0 +1,48 @@ +package oauth21_test + +import ( + "net/http" + "net/url" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/pomerium/pomerium/internal/oauth21" +) + +func TestParseTokenRequest_BasicAuth(t *testing.T) { + form := url.Values{} + form.Set("grant_type", "authorization_code") + form.Set("code", "abc") + req, err := http.NewRequest(http.MethodPost, "/token", strings.NewReader(form.Encode())) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.SetBasicAuth("myclient", "secret") + + tr, err := oauth21.ParseTokenRequest(req) + require.NoError(t, err) + require.NotNil(t, tr.ClientId) + require.Equal(t, "myclient", *tr.ClientId) + require.NotNil(t, tr.ClientSecret) + require.Equal(t, "secret", *tr.ClientSecret) +} + +func TestParseTokenRequest_BasicAuthWithBodyOverride(t *testing.T) { + form := url.Values{} + form.Set("grant_type", "authorization_code") + form.Set("code", "abc") + form.Set("client_id", "bodyid") + form.Set("client_secret", "bodysecret") + req, err := http.NewRequest(http.MethodPost, "/token", strings.NewReader(form.Encode())) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.SetBasicAuth("basicid", "basicsecret") + + tr, err := oauth21.ParseTokenRequest(req) + require.NoError(t, err) + require.NotNil(t, tr.ClientId) + require.Equal(t, "bodyid", *tr.ClientId) // body should win + require.NotNil(t, tr.ClientSecret) + require.Equal(t, "bodysecret", *tr.ClientSecret) +} diff --git a/internal/oauth21/validate_client.go b/internal/oauth21/validate_client.go index d34ca81b3..b5f7e4de8 100644 --- a/internal/oauth21/validate_client.go +++ b/internal/oauth21/validate_client.go @@ -1,6 +1,7 @@ package oauth21 import ( + "fmt" "slices" "github.com/pomerium/pomerium/internal/oauth21/gen" @@ -8,17 +9,45 @@ import ( ) func ValidateAuthorizationRequest( - client *rfc7591v1.ClientMetadata, + client *rfc7591v1.Metadata, req *gen.AuthorizationRequest, ) error { if err := ValidateAuthorizationRequestRedirectURI(client, req.RedirectUri); err != nil { return err } + if err := ValidateAuthorizationRequestCodeChallenge(client, req); err != nil { + return err + } + return nil +} + +func ValidateAuthorizationRequestCodeChallenge( + client *rfc7591v1.Metadata, + req *gen.AuthorizationRequest, +) error { + m := client.GetTokenEndpointAuthMethod() + switch m { + case rfc7591v1.TokenEndpointAuthMethodNone: + if req.GetCodeChallenge() == "" { + return Error{ + Code: InvalidRequest, + Description: "code challenge are required when token endpoint auth method is 'none'", + } + } + case rfc7591v1.TokenEndpointAuthMethodClientSecretBasic, + rfc7591v1.TokenEndpointAuthMethodClientSecretPost: + // code challenge is recommended but not required for these methods + default: + return Error{ + Code: InvalidRequest, + Description: fmt.Sprintf("unsupported token endpoint auth method: %s", m), + } + } return nil } func ValidateAuthorizationRequestRedirectURI( - client *rfc7591v1.ClientMetadata, + client *rfc7591v1.Metadata, redirectURI *string, ) error { if len(client.RedirectUris) == 0 { diff --git a/internal/oauth21/validate_client_test.go b/internal/oauth21/validate_client_test.go index 8b70e359a..bc4c86012 100644 --- a/internal/oauth21/validate_client_test.go +++ b/internal/oauth21/validate_client_test.go @@ -13,17 +13,67 @@ import ( func TestValidateRequest(t *testing.T) { t.Parallel() + + clientBasic := rfc7591v1.TokenEndpointAuthMethodClientSecretBasic + clientNone := rfc7591v1.TokenEndpointAuthMethodNone for _, tc := range []struct { name string - client *rfc7591v1.ClientMetadata + client *rfc7591v1.Metadata req *gen.AuthorizationRequest err bool }{ { - "optional redirect_uri, multiple redirect_uris", - &rfc7591v1.ClientMetadata{ + "default token auth method, no code challenge", + &rfc7591v1.Metadata{ RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"}, }, + &gen.AuthorizationRequest{ + RedirectUri: proto.String("https://example.com/callback"), + }, + true, + }, + { + "none token auth method, no code challenge", + &rfc7591v1.Metadata{ + RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"}, + TokenEndpointAuthMethod: &clientNone, + }, + &gen.AuthorizationRequest{ + RedirectUri: proto.String("https://example.com/callback"), + }, + true, + }, + { + "none token auth method, code challenge is provided", + &rfc7591v1.Metadata{ + RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"}, + TokenEndpointAuthMethod: &clientNone, + }, + &gen.AuthorizationRequest{ + RedirectUri: proto.String("https://example.com/callback"), + CodeChallenge: proto.String("challenge"), + }, + false, + }, + { + "none token auth method, code challenge and method are provided", + &rfc7591v1.Metadata{ + RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"}, + TokenEndpointAuthMethod: &clientNone, + }, + &gen.AuthorizationRequest{ + RedirectUri: proto.String("https://example.com/callback"), + CodeChallenge: proto.String("challenge"), + CodeChallengeMethod: proto.String("S256"), + }, + false, + }, + { + "optional redirect_uri, multiple redirect_uris", + &rfc7591v1.Metadata{ + RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"}, + TokenEndpointAuthMethod: &clientBasic, + }, &gen.AuthorizationRequest{ RedirectUri: nil, }, @@ -31,8 +81,9 @@ func TestValidateRequest(t *testing.T) { }, { "optional redirect_uri, single redirect_uri", - &rfc7591v1.ClientMetadata{ - RedirectUris: []string{"https://example.com/callback"}, + &rfc7591v1.Metadata{ + RedirectUris: []string{"https://example.com/callback"}, + TokenEndpointAuthMethod: &clientBasic, }, &gen.AuthorizationRequest{ RedirectUri: nil, @@ -41,8 +92,9 @@ func TestValidateRequest(t *testing.T) { }, { "matching redirect_uri", - &rfc7591v1.ClientMetadata{ - RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"}, + &rfc7591v1.Metadata{ + RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"}, + TokenEndpointAuthMethod: &clientBasic, }, &gen.AuthorizationRequest{ RedirectUri: proto.String("https://example.com/callback"), @@ -51,8 +103,9 @@ func TestValidateRequest(t *testing.T) { }, { "non-matching redirect_uri", - &rfc7591v1.ClientMetadata{ - RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"}, + &rfc7591v1.Metadata{ + RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"}, + TokenEndpointAuthMethod: &clientBasic, }, &gen.AuthorizationRequest{ RedirectUri: proto.String("https://example.com/invalid-callback"), diff --git a/internal/rfc7591/buf.lock b/internal/rfc7591/buf.lock index 09123d359..f49df773a 100644 --- a/internal/rfc7591/buf.lock +++ b/internal/rfc7591/buf.lock @@ -2,5 +2,5 @@ version: v2 deps: - name: buf.build/bufbuild/protovalidate - commit: 7712fb530c574b95bc1d57c0877543c3 - digest: b5:b3e9c9428384357e3b73e4d5a4614328b0a4b1595b10163bbe9483fa16204749274c41797bd49b0d716479c855aa35c1172a94f471fa120ba8369637fd138829 + commit: b52ab10f44684cb19d1fbcad56aedd36 + digest: b5:5f464399f5ea7546eb3c6a4e822a306da538298f3d87e9f974f4523d4955de396eb2b8b52a2f3f06ac290764d80cecbaa0a4c96560558e43d9b1c722e61a9d5c diff --git a/internal/rfc7591/format.go b/internal/rfc7591/format.go new file mode 100644 index 000000000..9b45d0432 --- /dev/null +++ b/internal/rfc7591/format.go @@ -0,0 +1,103 @@ +package rfc7591v1 + +import ( + "encoding/json" + "fmt" + "io" + + "github.com/bufbuild/protovalidate-go" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +const ( + TokenEndpointAuthMethodNone = "none" + TokenEndpointAuthMethodClientSecretBasic = "client_secret_basic" + TokenEndpointAuthMethodClientSecretPost = "client_secret_post" + + GrantTypesAuthorizationCode = "authorization_code" + GrantTypesImplicit = "implicit" + GrantTypesPassword = "password" + GrantTypesClientCredentials = "client_credentials" + GrantTypesRefreshToken = "refresh_token" + GrantTypesJWTBearer = "urn:ietf:params:oauth:grant-type:jwt-bearer" //nolint:gosec + GrantTypesSAML2Bearer = "urn:ietf:params:oauth:grant-type:saml2-bearer" //nolint:gosec + GrantTypesDeviceCode = "urn:ietf:params:oauth:grant-type:device_code" //nolint:gosec + + ResponseTypesCode = "code" + ResponseTypeToken = "token" +) + +func (v *Metadata) SetDefaults() { + if v.TokenEndpointAuthMethod == nil { + v.TokenEndpointAuthMethod = proto.String(TokenEndpointAuthMethodClientSecretBasic) + } + + if len(v.GrantTypes) == 0 { + v.GrantTypes = []string{GrantTypesAuthorizationCode} + } + + if len(v.ResponseTypes) == 0 { + v.ResponseTypes = []string{ResponseTypesCode} + } +} + +func (v *Metadata) Validate() error { + return protovalidate.Validate(v) +} + +func ParseMetadata( + data []byte, +) (*Metadata, error) { + v := new(Metadata) + err := protojson.UnmarshalOptions{ + AllowPartial: false, + DiscardUnknown: true, + }.Unmarshal(data, v) + if err != nil { + return nil, err + } + + return v, nil +} + +func WriteRegistrationResponse( + w io.Writer, + clientID string, + clientSecret *ClientSecret, + metadata *Metadata, +) error { + var metadataJSON map[string]any + if metadata == nil { + return fmt.Errorf("metadata cannot be nil") + } + metadataBytes, err := protojson.MarshalOptions{ + UseProtoNames: true, + EmitUnpopulated: false, + }.Marshal(metadata) + if err != nil { + return err + } + if err := json.Unmarshal(metadataBytes, &metadataJSON); err != nil { + return err + } + + metadataJSON["client_id"] = clientID + + if clientSecret != nil { + metadataJSON["client_secret"] = clientSecret.Value + if clientSecret.CreatedAt != nil { + metadataJSON["client_id_issued_at"] = clientSecret.CreatedAt.Seconds + } + + // Per RFC 7591: client_secret_expires_at is REQUIRED if client_secret is issued + // Value should be 0 if the secret doesn't expire + if clientSecret.ExpiresAt != nil { + metadataJSON["client_secret_expires_at"] = clientSecret.ExpiresAt.Seconds + } else { + metadataJSON["client_secret_expires_at"] = int64(0) + } + } + + return json.NewEncoder(w).Encode(metadataJSON) +} diff --git a/internal/rfc7591/format_test.go b/internal/rfc7591/format_test.go new file mode 100644 index 000000000..81dedcd2e --- /dev/null +++ b/internal/rfc7591/format_test.go @@ -0,0 +1,540 @@ +package rfc7591v1 + +import ( + "bytes" + "encoding/json" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestParseMetadata(t *testing.T) { + tests := []struct { + name string + input string + want *Metadata + wantErr bool + }{ + { + name: "minimal valid metadata", + input: `{ + "redirect_uris": ["https://example.com/callback"] + }`, + want: &Metadata{ + RedirectUris: []string{"https://example.com/callback"}, + TokenEndpointAuthMethod: proto.String(TokenEndpointAuthMethodClientSecretBasic), + GrantTypes: []string{GrantTypesAuthorizationCode}, + ResponseTypes: []string{ResponseTypesCode}, + }, + wantErr: false, + }, + { + name: "full metadata with all fields", + input: `{ + "redirect_uris": ["https://example.com/callback", "https://example.com/callback2"], + "token_endpoint_auth_method": "client_secret_post", + "grant_types": ["authorization_code", "refresh_token"], + "response_types": ["code"], + "client_name": "Test Client", + "client_name_localized": {"en": "Test Client", "es": "Cliente de Prueba"}, + "client_uri": "https://example.com", + "client_uri_localized": {"en": "https://example.com/en"}, + "logo_uri": "https://example.com/logo.png", + "scope": "openid profile email", + "contacts": ["admin@example.com", "support@example.com"], + "tos_uri": "https://example.com/tos", + "policy_uri": "https://example.com/privacy", + "jwks_uri": "https://example.com/.well-known/jwks.json", + "software_id": "test-client-v1", + "software_version": "1.0.0" + }`, + want: &Metadata{ + RedirectUris: []string{"https://example.com/callback", "https://example.com/callback2"}, + TokenEndpointAuthMethod: proto.String("client_secret_post"), + GrantTypes: []string{"authorization_code", "refresh_token"}, + ResponseTypes: []string{"code"}, + ClientName: proto.String("Test Client"), + ClientNameLocalized: map[string]string{ + "en": "Test Client", + "es": "Cliente de Prueba", + }, + ClientUri: proto.String("https://example.com"), + ClientUriLocalized: map[string]string{ + "en": "https://example.com/en", + }, + LogoUri: proto.String("https://example.com/logo.png"), + Scope: proto.String("openid profile email"), + Contacts: []string{"admin@example.com", "support@example.com"}, + TosUri: proto.String("https://example.com/tos"), + PolicyUri: proto.String("https://example.com/privacy"), + JwksUri: proto.String("https://example.com/.well-known/jwks.json"), + SoftwareId: proto.String("test-client-v1"), + SoftwareVersion: proto.String("1.0.0"), + }, + wantErr: false, + }, + { + name: "metadata with jwks instead of jwks_uri", + input: `{ + "redirect_uris": ["https://example.com/callback"], + "jwks": { + "keys": [{ + "kty": "RSA", + "rsa_params": { + "n": "example-modulus", + "e": "AQAB" + } + }] + } + }`, + want: &Metadata{ + RedirectUris: []string{"https://example.com/callback"}, + TokenEndpointAuthMethod: proto.String(TokenEndpointAuthMethodClientSecretBasic), + GrantTypes: []string{GrantTypesAuthorizationCode}, + ResponseTypes: []string{ResponseTypesCode}, + Jwks: &JsonWebKeySet{ + Keys: []*JsonWebKey{{ + Kty: "RSA", + KeyTypeParameters: &JsonWebKey_RsaParams{ + RsaParams: &RsaKeyParameters{ + N: "example-modulus", + E: "AQAB", + }, + }, + }}, + }, + }, + wantErr: false, + }, + { + name: "explicit token_endpoint_auth_method none", + input: `{ + "redirect_uris": ["https://example.com/callback"], + "token_endpoint_auth_method": "none" + }`, + want: &Metadata{ + RedirectUris: []string{"https://example.com/callback"}, + TokenEndpointAuthMethod: proto.String("none"), + GrantTypes: []string{GrantTypesAuthorizationCode}, + ResponseTypes: []string{ResponseTypesCode}, + }, + wantErr: false, + }, + { + name: "custom grant and response types", + input: `{ + "redirect_uris": ["https://example.com/callback"], + "grant_types": ["implicit", "client_credentials"], + "response_types": ["token", "code"] + }`, + want: &Metadata{ + RedirectUris: []string{"https://example.com/callback"}, + TokenEndpointAuthMethod: proto.String(TokenEndpointAuthMethodClientSecretBasic), + GrantTypes: []string{"implicit", "client_credentials"}, + ResponseTypes: []string{"token", "code"}, + }, + wantErr: false, + }, + { + name: "empty input", + input: "", + wantErr: true, + }, + { + name: "invalid JSON", + input: `{"redirect_uris": [}`, + wantErr: true, + }, + { + name: "missing required redirect_uris", + input: `{ + "client_name": "Test Client" + }`, + wantErr: true, + }, + { + name: "empty redirect_uris array", + input: `{ + "redirect_uris": [] + }`, + wantErr: true, + }, + { + name: "invalid redirect_uri", + input: `{ + "redirect_uris": ["not-a-uri"] + }`, + wantErr: true, + }, + { + name: "invalid token_endpoint_auth_method", + input: `{ + "redirect_uris": ["https://example.com/callback"], + "token_endpoint_auth_method": "invalid_method" + }`, + wantErr: true, + }, + { + name: "invalid email in contacts", + input: `{ + "redirect_uris": ["https://example.com/callback"], + "contacts": ["not-an-email"] + }`, + wantErr: true, + }, + { + name: "invalid scope format", + input: `{ + "redirect_uris": ["https://example.com/callback"], + "scope": " invalid spaces " + }`, + wantErr: true, + }, + { + name: "both jwks and jwks_uri provided", + input: `{ + "redirect_uris": ["https://example.com/callback"], + "jwks_uri": "https://example.com/.well-known/jwks.json", + "jwks": { + "keys": [{ + "kty": "RSA", + "rsa_params": { + "n": "example-modulus", + "e": "AQAB" + } + }] + } + }`, + wantErr: true, + }, + { + name: "client_name too long", + input: `{ + "redirect_uris": ["https://example.com/callback"], + "client_name": "` + strings.Repeat("a", 256) + `" + }`, + wantErr: true, + }, + { + name: "discard unknown fields", + input: `{ + "redirect_uris": ["https://example.com/callback"], + "unknown_field": "should be ignored", + "another_unknown": 123 + }`, + want: &Metadata{ + RedirectUris: []string{"https://example.com/callback"}, + TokenEndpointAuthMethod: proto.String(TokenEndpointAuthMethodClientSecretBasic), + GrantTypes: []string{GrantTypesAuthorizationCode}, + ResponseTypes: []string{ResponseTypesCode}, + }, + wantErr: false, + }, + { + name: "invalid BCP 47 language tag - segment too long", + input: `{ + "redirect_uris": ["https://example.com/callback"], + "client_name_localized": {"toolongtagsegment": "Test Client"} + }`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseMetadata([]byte(tt.input)) + if err == nil && got != nil { + got.SetDefaults() + err = got.Validate() + } + + if tt.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + return + } + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if diff := cmp.Diff(tt.want, got, protocmp.Transform()); diff != "" { + t.Errorf("ParseMetadata() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestWriteRegistrationResponse(t *testing.T) { + // Test timestamp for consistent testing + testTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) + testTimestamp := timestamppb.New(testTime) + + tests := []struct { + name string + clientID string + clientSecret *ClientSecret + metadata *Metadata + want map[string]any + wantErr bool + }{ + { + name: "minimal response without client secret", + clientID: "test-client-123", + metadata: &Metadata{ + RedirectUris: []string{"https://example.com/callback"}, + TokenEndpointAuthMethod: proto.String(TokenEndpointAuthMethodNone), + GrantTypes: []string{GrantTypesAuthorizationCode}, + ResponseTypes: []string{ResponseTypesCode}, + }, + want: map[string]any{ + "client_id": "test-client-123", + "redirect_uris": []any{"https://example.com/callback"}, + "token_endpoint_auth_method": TokenEndpointAuthMethodNone, + "grant_types": []any{GrantTypesAuthorizationCode}, + "response_types": []any{ResponseTypesCode}, + }, + }, + { + name: "response with client secret and timestamps", + clientID: "test-client-456", + clientSecret: &ClientSecret{ + Value: "super-secret-value", + CreatedAt: testTimestamp, + ExpiresAt: timestamppb.New(testTime.Add(24 * time.Hour)), + }, + metadata: &Metadata{ + RedirectUris: []string{"https://example.com/callback"}, + TokenEndpointAuthMethod: proto.String(TokenEndpointAuthMethodClientSecretPost), + GrantTypes: []string{GrantTypesAuthorizationCode, GrantTypesRefreshToken}, + ResponseTypes: []string{ResponseTypesCode}, + ClientName: proto.String("Test Client"), + Scope: proto.String("openid profile email"), + }, + want: map[string]any{ + "client_id": "test-client-456", + "client_secret": "super-secret-value", + "client_id_issued_at": float64(testTime.Unix()), + "client_secret_expires_at": float64(testTime.Add(24 * time.Hour).Unix()), + "redirect_uris": []any{"https://example.com/callback"}, + "token_endpoint_auth_method": TokenEndpointAuthMethodClientSecretPost, + "grant_types": []any{GrantTypesAuthorizationCode, GrantTypesRefreshToken}, + "response_types": []any{ResponseTypesCode}, + "client_name": "Test Client", + "scope": "openid profile email", + }, + }, + { + name: "response with client secret but no timestamps", + clientID: "test-client-789", + clientSecret: &ClientSecret{ + Value: "another-secret", + // CreatedAt and ExpiresAt are nil + }, + metadata: &Metadata{ + RedirectUris: []string{"https://example.com/callback"}, + TokenEndpointAuthMethod: proto.String(TokenEndpointAuthMethodClientSecretBasic), + GrantTypes: []string{GrantTypesClientCredentials}, + ResponseTypes: []string{ResponseTypesCode}, + }, + want: map[string]any{ + "client_id": "test-client-789", + "client_secret": "another-secret", + "client_secret_expires_at": float64(0), // Required per RFC when client_secret is present + "redirect_uris": []any{"https://example.com/callback"}, + "token_endpoint_auth_method": TokenEndpointAuthMethodClientSecretBasic, + "grant_types": []any{GrantTypesClientCredentials}, + "response_types": []any{ResponseTypesCode}, + }, + }, + { + name: "response with full metadata", + clientID: "full-client-id", + clientSecret: &ClientSecret{ + Value: "full-secret", + CreatedAt: testTimestamp, + }, + metadata: &Metadata{ + RedirectUris: []string{"https://example.com/cb1", "https://example.com/cb2"}, + TokenEndpointAuthMethod: proto.String(TokenEndpointAuthMethodClientSecretPost), + GrantTypes: []string{GrantTypesAuthorizationCode, GrantTypesImplicit}, + ResponseTypes: []string{ResponseTypesCode, ResponseTypeToken}, + ClientName: proto.String("Full Test Client"), + ClientNameLocalized: map[string]string{ + "en": "Full Test Client", + "es": "Cliente de Prueba Completo", + }, + ClientUri: proto.String("https://example.com"), + ClientUriLocalized: map[string]string{ + "en": "https://example.com/en", + }, + LogoUri: proto.String("https://example.com/logo.png"), + Scope: proto.String("openid profile email offline_access"), + Contacts: []string{"admin@example.com", "support@example.com"}, + TosUri: proto.String("https://example.com/tos"), + PolicyUri: proto.String("https://example.com/privacy"), + JwksUri: proto.String("https://example.com/.well-known/jwks.json"), + SoftwareId: proto.String("test-software-v1"), + SoftwareVersion: proto.String("1.2.3"), + }, + want: map[string]any{ + "client_id": "full-client-id", + "client_secret": "full-secret", + "client_id_issued_at": float64(testTime.Unix()), + "client_secret_expires_at": float64(0), // Required per RFC, 0 means no expiration + "redirect_uris": []any{"https://example.com/cb1", "https://example.com/cb2"}, + "token_endpoint_auth_method": TokenEndpointAuthMethodClientSecretPost, + "grant_types": []any{GrantTypesAuthorizationCode, GrantTypesImplicit}, + "response_types": []any{ResponseTypesCode, ResponseTypeToken}, + "client_name": "Full Test Client", + "client_name_localized": map[string]any{ + "en": "Full Test Client", + "es": "Cliente de Prueba Completo", + }, + "client_uri": "https://example.com", + "client_uri_localized": map[string]any{ + "en": "https://example.com/en", + }, + "logo_uri": "https://example.com/logo.png", + "scope": "openid profile email offline_access", + "contacts": []any{"admin@example.com", "support@example.com"}, + "tos_uri": "https://example.com/tos", + "policy_uri": "https://example.com/privacy", + "jwks_uri": "https://example.com/.well-known/jwks.json", + "software_id": "test-software-v1", + "software_version": "1.2.3", + }, + }, + { + name: "response with jwks instead of jwks_uri", + clientID: "jwks-client", + metadata: &Metadata{ + RedirectUris: []string{"https://example.com/callback"}, + TokenEndpointAuthMethod: proto.String(TokenEndpointAuthMethodNone), + GrantTypes: []string{GrantTypesAuthorizationCode}, + ResponseTypes: []string{ResponseTypesCode}, + Jwks: &JsonWebKeySet{ + Keys: []*JsonWebKey{{ + Kty: "RSA", + KeyTypeParameters: &JsonWebKey_RsaParams{ + RsaParams: &RsaKeyParameters{ + N: "example-modulus", + E: "AQAB", + }, + }, + }}, + }, + }, + want: map[string]any{ + "client_id": "jwks-client", + "redirect_uris": []any{"https://example.com/callback"}, + "token_endpoint_auth_method": TokenEndpointAuthMethodNone, + "grant_types": []any{GrantTypesAuthorizationCode}, + "response_types": []any{ResponseTypesCode}, + "jwks": map[string]any{ + "keys": []any{ + map[string]any{ + "kty": "RSA", + "rsa_params": map[string]any{ + "n": "example-modulus", + "e": "AQAB", + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + err := WriteRegistrationResponse(&buf, tt.clientID, tt.clientSecret, tt.metadata) + + if tt.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + return + } + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Parse the JSON output + var got map[string]any + if err := json.Unmarshal(buf.Bytes(), &got); err != nil { + t.Fatalf("failed to parse JSON output: %v", err) + } + + // Compare the result + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("WriteRegistrationResponse() mismatch (-want +got):\n%s", diff) + } + + // Verify that the output is valid JSON + if !json.Valid(buf.Bytes()) { + t.Error("output is not valid JSON") + } + + // Verify that client_secret is only present when expected + _, hasClientSecret := got["client_secret"] + expectedHasSecret := tt.clientSecret != nil + if hasClientSecret != expectedHasSecret { + t.Errorf("client_secret presence mismatch: got %v, want %v", hasClientSecret, expectedHasSecret) + } + + // Verify timestamp fields presence per RFC 7591 + _, hasIssuedAt := got["client_id_issued_at"] + _, hasExpiresAt := got["client_secret_expires_at"] + + expectedHasIssuedAt := tt.clientSecret != nil && tt.clientSecret.CreatedAt != nil + // Per RFC 7591: client_secret_expires_at is REQUIRED if client_secret is issued + expectedHasExpiresAt := tt.clientSecret != nil + + if hasIssuedAt != expectedHasIssuedAt { + t.Errorf("client_id_issued_at presence mismatch: got %v, want %v", hasIssuedAt, expectedHasIssuedAt) + } + if hasExpiresAt != expectedHasExpiresAt { + t.Errorf("client_secret_expires_at presence mismatch: got %v, want %v", hasExpiresAt, expectedHasExpiresAt) + } + }) + } +} + +func TestWriteRegistrationResponseEdgeCases(t *testing.T) { + t.Run("nil metadata", func(t *testing.T) { + var buf bytes.Buffer + err := WriteRegistrationResponse(&buf, "test-client", nil, nil) + if err == nil { + t.Fatalf("expected error with nil metadata: %v", err) + } + }) + + t.Run("empty client ID", func(t *testing.T) { + var buf bytes.Buffer + metadata := &Metadata{ + RedirectUris: []string{"https://example.com/callback"}, + } + err := WriteRegistrationResponse(&buf, "", nil, metadata) + if err != nil { + t.Fatalf("unexpected error with empty client ID: %v", err) + } + + var got map[string]any + if err := json.Unmarshal(buf.Bytes(), &got); err != nil { + t.Fatalf("failed to parse JSON output: %v", err) + } + + if got["client_id"] != "" { + t.Errorf("expected empty client_id, got %v", got["client_id"]) + } + }) +} diff --git a/internal/rfc7591/types.pb.go b/internal/rfc7591/types.pb.go index 6a3c48c7f..3b646b9c1 100644 --- a/internal/rfc7591/types.pb.go +++ b/internal/rfc7591/types.pb.go @@ -10,6 +10,7 @@ import ( _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -470,7 +471,7 @@ func (x *OkpKeyParameters) GetX() string { // Represents the client metadata fields defined in RFC 7591 Section 2. // These values are used both as input to registration requests and output in // registration responses. -type ClientMetadata struct { +type Metadata struct { state protoimpl.MessageState `protogen:"open.v1"` // Array of redirection URI strings. REQUIRED for clients using flows with // redirection. @@ -525,20 +526,20 @@ type ClientMetadata struct { sizeCache protoimpl.SizeCache } -func (x *ClientMetadata) Reset() { - *x = ClientMetadata{} +func (x *Metadata) Reset() { + *x = Metadata{} mi := &file_types_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ClientMetadata) String() string { +func (x *Metadata) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ClientMetadata) ProtoMessage() {} +func (*Metadata) ProtoMessage() {} -func (x *ClientMetadata) ProtoReflect() protoreflect.Message { +func (x *Metadata) ProtoReflect() protoreflect.Message { mi := &file_types_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -550,414 +551,554 @@ func (x *ClientMetadata) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ClientMetadata.ProtoReflect.Descriptor instead. -func (*ClientMetadata) Descriptor() ([]byte, []int) { +// Deprecated: Use Metadata.ProtoReflect.Descriptor instead. +func (*Metadata) Descriptor() ([]byte, []int) { return file_types_proto_rawDescGZIP(), []int{6} } -func (x *ClientMetadata) GetRedirectUris() []string { +func (x *Metadata) GetRedirectUris() []string { if x != nil { return x.RedirectUris } return nil } -func (x *ClientMetadata) GetTokenEndpointAuthMethod() string { +func (x *Metadata) GetTokenEndpointAuthMethod() string { if x != nil && x.TokenEndpointAuthMethod != nil { return *x.TokenEndpointAuthMethod } return "" } -func (x *ClientMetadata) GetGrantTypes() []string { +func (x *Metadata) GetGrantTypes() []string { if x != nil { return x.GrantTypes } return nil } -func (x *ClientMetadata) GetResponseTypes() []string { +func (x *Metadata) GetResponseTypes() []string { if x != nil { return x.ResponseTypes } return nil } -func (x *ClientMetadata) GetClientName() string { +func (x *Metadata) GetClientName() string { if x != nil && x.ClientName != nil { return *x.ClientName } return "" } -func (x *ClientMetadata) GetClientNameLocalized() map[string]string { +func (x *Metadata) GetClientNameLocalized() map[string]string { if x != nil { return x.ClientNameLocalized } return nil } -func (x *ClientMetadata) GetClientUri() string { +func (x *Metadata) GetClientUri() string { if x != nil && x.ClientUri != nil { return *x.ClientUri } return "" } -func (x *ClientMetadata) GetClientUriLocalized() map[string]string { +func (x *Metadata) GetClientUriLocalized() map[string]string { if x != nil { return x.ClientUriLocalized } return nil } -func (x *ClientMetadata) GetLogoUri() string { +func (x *Metadata) GetLogoUri() string { if x != nil && x.LogoUri != nil { return *x.LogoUri } return "" } -func (x *ClientMetadata) GetLogoUriLocalized() map[string]string { +func (x *Metadata) GetLogoUriLocalized() map[string]string { if x != nil { return x.LogoUriLocalized } return nil } -func (x *ClientMetadata) GetScope() string { +func (x *Metadata) GetScope() string { if x != nil && x.Scope != nil { return *x.Scope } return "" } -func (x *ClientMetadata) GetContacts() []string { +func (x *Metadata) GetContacts() []string { if x != nil { return x.Contacts } return nil } -func (x *ClientMetadata) GetTosUri() string { +func (x *Metadata) GetTosUri() string { if x != nil && x.TosUri != nil { return *x.TosUri } return "" } -func (x *ClientMetadata) GetTosUriLocalized() map[string]string { +func (x *Metadata) GetTosUriLocalized() map[string]string { if x != nil { return x.TosUriLocalized } return nil } -func (x *ClientMetadata) GetPolicyUri() string { +func (x *Metadata) GetPolicyUri() string { if x != nil && x.PolicyUri != nil { return *x.PolicyUri } return "" } -func (x *ClientMetadata) GetPolicyUriLocalized() map[string]string { +func (x *Metadata) GetPolicyUriLocalized() map[string]string { if x != nil { return x.PolicyUriLocalized } return nil } -func (x *ClientMetadata) GetJwksUri() string { +func (x *Metadata) GetJwksUri() string { if x != nil && x.JwksUri != nil { return *x.JwksUri } return "" } -func (x *ClientMetadata) GetJwks() *JsonWebKeySet { +func (x *Metadata) GetJwks() *JsonWebKeySet { if x != nil { return x.Jwks } return nil } -func (x *ClientMetadata) GetSoftwareId() string { +func (x *Metadata) GetSoftwareId() string { if x != nil && x.SoftwareId != nil { return *x.SoftwareId } return "" } -func (x *ClientMetadata) GetSoftwareVersion() string { +func (x *Metadata) GetSoftwareVersion() string { if x != nil && x.SoftwareVersion != nil { return *x.SoftwareVersion } return "" } +type ClientSecret struct { + state protoimpl.MessageState `protogen:"open.v1"` + // REQUIRED. The client secret value. + Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` + // OPTIONAL. The expiration time of the client secret. + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expires_at,json=expiresAt,proto3,oneof" json:"expires_at,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientSecret) Reset() { + *x = ClientSecret{} + mi := &file_types_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientSecret) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientSecret) ProtoMessage() {} + +func (x *ClientSecret) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientSecret.ProtoReflect.Descriptor instead. +func (*ClientSecret) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{7} +} + +func (x *ClientSecret) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +func (x *ClientSecret) GetExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.ExpiresAt + } + return nil +} + +func (x *ClientSecret) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +// Represents the client registration storage structure. +type ClientRegistration struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Contains the client metadata as requested by the client. + RequestMetadata *Metadata `protobuf:"bytes,1,opt,name=request_metadata,json=requestMetadata,proto3" json:"request_metadata,omitempty"` + // Contains the client metadata as was returned by the server. + ResponseMetadata *Metadata `protobuf:"bytes,2,opt,name=response_metadata,json=responseMetadata,proto3" json:"response_metadata,omitempty"` + // OPTIONAL. The "client_secret" parameter is the secret used by the client + ClientSecret *ClientSecret `protobuf:"bytes,3,opt,name=client_secret,json=clientSecret,proto3" json:"client_secret,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientRegistration) Reset() { + *x = ClientRegistration{} + mi := &file_types_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientRegistration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientRegistration) ProtoMessage() {} + +func (x *ClientRegistration) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientRegistration.ProtoReflect.Descriptor instead. +func (*ClientRegistration) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{8} +} + +func (x *ClientRegistration) GetRequestMetadata() *Metadata { + if x != nil { + return x.RequestMetadata + } + return nil +} + +func (x *ClientRegistration) GetResponseMetadata() *Metadata { + if x != nil { + return x.ResponseMetadata + } + return nil +} + +func (x *ClientRegistration) GetClientSecret() *ClientSecret { + if x != nil { + return x.ClientSecret + } + return nil +} + var File_types_proto protoreflect.FileDescriptor var file_types_proto_rawDesc = string([]byte{ 0x0a, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, - 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4d, 0x0a, 0x0d, 0x4a, - 0x73, 0x6f, 0x6e, 0x57, 0x65, 0x62, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x74, 0x12, 0x3c, 0x0a, 0x04, - 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x65, 0x74, - 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x2e, 0x4a, 0x73, 0x6f, - 0x6e, 0x57, 0x65, 0x62, 0x4b, 0x65, 0x79, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xc8, 0x01, 0x01, 0x92, - 0x01, 0x02, 0x08, 0x01, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22, 0xcf, 0x07, 0x0a, 0x0a, 0x4a, - 0x73, 0x6f, 0x6e, 0x57, 0x65, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x03, 0x6b, 0x74, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1d, 0xba, 0x48, 0x1a, 0xc8, 0x01, 0x01, 0x72, 0x15, - 0x10, 0x01, 0x52, 0x03, 0x52, 0x53, 0x41, 0x52, 0x02, 0x45, 0x43, 0x52, 0x03, 0x6f, 0x63, 0x74, - 0x52, 0x03, 0x4f, 0x4b, 0x50, 0x52, 0x03, 0x6b, 0x74, 0x79, 0x12, 0x26, 0x0a, 0x03, 0x75, 0x73, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0f, 0xba, 0x48, 0x0c, 0x72, 0x0a, 0x52, 0x03, - 0x73, 0x69, 0x67, 0x52, 0x03, 0x65, 0x6e, 0x63, 0x48, 0x01, 0x52, 0x03, 0x75, 0x73, 0x65, 0x88, - 0x01, 0x01, 0x12, 0x25, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6f, 0x70, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x09, 0x42, 0x0c, 0xba, 0x48, 0x09, 0x92, 0x01, 0x06, 0x22, 0x04, 0x72, 0x02, 0x10, - 0x01, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4f, 0x70, 0x73, 0x12, 0x1e, 0x0a, 0x03, 0x61, 0x6c, 0x67, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x48, - 0x02, 0x52, 0x03, 0x61, 0x6c, 0x67, 0x88, 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x03, 0x6b, 0x69, 0x64, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x48, - 0x03, 0x52, 0x03, 0x6b, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x42, 0x0a, 0x0a, 0x72, 0x73, 0x61, - 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, - 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x73, 0x61, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, - 0x48, 0x00, 0x52, 0x09, 0x72, 0x73, 0x61, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x3f, 0x0a, - 0x09, 0x65, 0x63, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x20, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, - 0x76, 0x31, 0x2e, 0x45, 0x63, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, - 0x72, 0x73, 0x48, 0x00, 0x52, 0x08, 0x65, 0x63, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x54, - 0x0a, 0x10, 0x73, 0x79, 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x70, 0x61, 0x72, 0x61, - 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, - 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6d, 0x6d, 0x65, - 0x74, 0x72, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x73, 0x48, 0x00, 0x52, 0x0f, 0x73, 0x79, 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x50, 0x61, - 0x72, 0x61, 0x6d, 0x73, 0x12, 0x42, 0x0a, 0x0a, 0x6f, 0x6b, 0x70, 0x5f, 0x70, 0x61, 0x72, 0x61, - 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, - 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6b, 0x70, 0x4b, 0x65, - 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x09, 0x6f, - 0x6b, 0x70, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x3a, 0xab, 0x03, 0xba, 0x48, 0xa7, 0x03, 0x1a, - 0x66, 0x0a, 0x12, 0x6a, 0x77, 0x6b, 0x2e, 0x6b, 0x74, 0x79, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, - 0x73, 0x2e, 0x72, 0x73, 0x61, 0x12, 0x25, 0x72, 0x73, 0x61, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, - 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x66, - 0x6f, 0x72, 0x20, 0x6b, 0x74, 0x79, 0x20, 0x27, 0x52, 0x53, 0x41, 0x27, 0x1a, 0x29, 0x74, 0x68, - 0x69, 0x73, 0x2e, 0x6b, 0x74, 0x79, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x52, 0x53, 0x41, 0x27, 0x20, - 0x7c, 0x7c, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x72, 0x73, 0x61, 0x5f, - 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x29, 0x1a, 0x61, 0x0a, 0x11, 0x6a, 0x77, 0x6b, 0x2e, 0x6b, - 0x74, 0x79, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x65, 0x63, 0x12, 0x23, 0x65, 0x63, - 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, - 0x69, 0x72, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6b, 0x74, 0x79, 0x20, 0x27, 0x45, 0x43, - 0x27, 0x1a, 0x27, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x74, 0x79, 0x20, 0x21, 0x3d, 0x20, 0x27, - 0x45, 0x43, 0x27, 0x20, 0x7c, 0x7c, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, - 0x65, 0x63, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x29, 0x1a, 0x72, 0x0a, 0x12, 0x6a, 0x77, - 0x6b, 0x2e, 0x6b, 0x74, 0x79, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x6f, 0x63, 0x74, - 0x12, 0x2b, 0x73, 0x79, 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x70, 0x61, 0x72, 0x61, - 0x6d, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, - 0x66, 0x6f, 0x72, 0x20, 0x6b, 0x74, 0x79, 0x20, 0x27, 0x6f, 0x63, 0x74, 0x27, 0x1a, 0x2f, 0x74, - 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x74, 0x79, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x6f, 0x63, 0x74, 0x27, - 0x20, 0x7c, 0x7c, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x73, 0x79, 0x6d, - 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x29, 0x1a, 0x66, - 0x0a, 0x12, 0x6a, 0x77, 0x6b, 0x2e, 0x6b, 0x74, 0x79, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, - 0x2e, 0x6f, 0x6b, 0x70, 0x12, 0x25, 0x6f, 0x6b, 0x70, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, - 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x66, 0x6f, - 0x72, 0x20, 0x6b, 0x74, 0x79, 0x20, 0x27, 0x4f, 0x4b, 0x50, 0x27, 0x1a, 0x29, 0x74, 0x68, 0x69, - 0x73, 0x2e, 0x6b, 0x74, 0x79, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x4f, 0x4b, 0x50, 0x27, 0x20, 0x7c, - 0x7c, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6f, 0x6b, 0x70, 0x5f, 0x70, - 0x61, 0x72, 0x61, 0x6d, 0x73, 0x29, 0x42, 0x1c, 0x0a, 0x13, 0x6b, 0x65, 0x79, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x05, 0xba, - 0x48, 0x02, 0x08, 0x00, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x75, 0x73, 0x65, 0x42, 0x06, 0x0a, 0x04, - 0x5f, 0x61, 0x6c, 0x67, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x6b, 0x69, 0x64, 0x22, 0x46, 0x0a, 0x10, - 0x52, 0x73, 0x61, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, - 0x12, 0x18, 0x0a, 0x01, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, - 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x01, 0x6e, 0x12, 0x18, 0x0a, 0x01, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, - 0x01, 0x52, 0x01, 0x65, 0x22, 0x76, 0x0a, 0x0f, 0x45, 0x63, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2f, 0x0a, 0x03, 0x63, 0x72, 0x76, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x1d, 0xba, 0x48, 0x1a, 0xc8, 0x01, 0x01, 0x72, 0x15, 0x52, 0x05, - 0x50, 0x2d, 0x32, 0x35, 0x36, 0x52, 0x05, 0x50, 0x2d, 0x33, 0x38, 0x34, 0x52, 0x05, 0x50, 0x2d, - 0x35, 0x32, 0x31, 0x52, 0x03, 0x63, 0x72, 0x76, 0x12, 0x18, 0x0a, 0x01, 0x78, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, - 0x01, 0x78, 0x12, 0x18, 0x0a, 0x01, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, - 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x01, 0x79, 0x22, 0x32, 0x0a, 0x16, - 0x53, 0x79, 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x01, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x01, 0x6b, - 0x22, 0x7e, 0x0a, 0x10, 0x4f, 0x6b, 0x70, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, - 0x74, 0x65, 0x72, 0x73, 0x12, 0x38, 0x0a, 0x03, 0x63, 0x72, 0x76, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x26, 0xba, 0x48, 0x23, 0xc8, 0x01, 0x01, 0x72, 0x1e, 0x52, 0x07, 0x45, 0x64, 0x32, - 0x35, 0x35, 0x31, 0x39, 0x52, 0x05, 0x45, 0x64, 0x34, 0x34, 0x38, 0x52, 0x06, 0x58, 0x32, 0x35, - 0x35, 0x31, 0x39, 0x52, 0x04, 0x58, 0x34, 0x34, 0x38, 0x52, 0x03, 0x63, 0x72, 0x76, 0x12, 0x30, - 0x0a, 0x01, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x22, 0xba, 0x48, 0x1f, 0xc8, 0x01, - 0x01, 0x72, 0x1a, 0x10, 0x01, 0x32, 0x16, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, - 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x3d, 0x7b, 0x30, 0x2c, 0x32, 0x7d, 0x24, 0x52, 0x01, 0x78, - 0x22, 0x9b, 0x13, 0x0a, 0x0e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x12, 0x36, 0x0a, 0x0d, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, - 0x75, 0x72, 0x69, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x11, 0xba, 0x48, 0x0e, 0x92, - 0x01, 0x0b, 0x08, 0x01, 0x22, 0x07, 0x72, 0x05, 0x10, 0x01, 0x88, 0x01, 0x01, 0x52, 0x0c, 0x72, - 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x69, 0x73, 0x12, 0x76, 0x0a, 0x1a, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x75, - 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x34, 0xba, 0x48, 0x31, 0x72, 0x2f, 0x52, 0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x52, 0x12, 0x63, 0x6c, - 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x73, 0x74, - 0x52, 0x13, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f, - 0x62, 0x61, 0x73, 0x69, 0x63, 0x48, 0x00, 0x52, 0x17, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, - 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x88, 0x01, 0x01, 0x12, 0xd7, 0x01, 0x0a, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x42, 0xb5, 0x01, 0xba, 0x48, 0xb1, 0x01, - 0x92, 0x01, 0xad, 0x01, 0x22, 0xaa, 0x01, 0x72, 0xa7, 0x01, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x52, 0x08, - 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, - 0x72, 0x64, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x2b, 0x75, 0x72, 0x6e, 0x3a, 0x69, 0x65, 0x74, 0x66, 0x3a, - 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x3a, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x3a, 0x67, 0x72, 0x61, - 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x6a, 0x77, 0x74, 0x2d, 0x62, 0x65, 0x61, 0x72, - 0x65, 0x72, 0x52, 0x2d, 0x75, 0x72, 0x6e, 0x3a, 0x69, 0x65, 0x74, 0x66, 0x3a, 0x70, 0x61, 0x72, - 0x61, 0x6d, 0x73, 0x3a, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x3a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x2d, - 0x74, 0x79, 0x70, 0x65, 0x3a, 0x73, 0x61, 0x6d, 0x6c, 0x32, 0x2d, 0x62, 0x65, 0x61, 0x72, 0x65, - 0x72, 0x52, 0x0a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x3e, 0x0a, - 0x0e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x09, 0x42, 0x17, 0xba, 0x48, 0x14, 0x92, 0x01, 0x11, 0x22, 0x0f, 0x72, - 0x0d, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x0d, - 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x30, 0x0a, - 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x48, 0x01, - 0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, - 0xa6, 0x01, 0x0a, 0x15, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, - 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x38, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, - 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x38, 0xba, 0x48, 0x35, 0x9a, 0x01, - 0x32, 0x22, 0x27, 0x72, 0x25, 0x32, 0x23, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x5d, - 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x28, 0x2d, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, - 0x39, 0x5d, 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x29, 0x2a, 0x24, 0x2a, 0x07, 0x72, 0x05, 0x10, 0x01, - 0x18, 0xff, 0x01, 0x52, 0x13, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x4c, - 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x0a, 0x63, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, - 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x48, 0x02, 0x52, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x55, 0x72, 0x69, 0x88, 0x01, 0x01, 0x12, 0xa1, 0x01, 0x0a, 0x14, 0x63, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x5f, 0x75, 0x72, 0x69, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, - 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, - 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x69, - 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x36, - 0xba, 0x48, 0x33, 0x9a, 0x01, 0x30, 0x22, 0x27, 0x72, 0x25, 0x32, 0x23, 0x5e, 0x5b, 0x61, 0x2d, - 0x7a, 0x41, 0x2d, 0x5a, 0x5d, 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x28, 0x2d, 0x5b, 0x61, 0x2d, 0x7a, - 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5d, 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x29, 0x2a, 0x24, 0x2a, - 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x55, 0x72, - 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x28, 0x0a, 0x08, 0x6c, 0x6f, - 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, - 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x48, 0x03, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, - 0x69, 0x88, 0x01, 0x01, 0x12, 0x9b, 0x01, 0x0a, 0x12, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, - 0x69, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x35, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, - 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x36, 0xba, 0x48, 0x33, 0x9a, 0x01, 0x30, - 0x22, 0x27, 0x72, 0x25, 0x32, 0x23, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x5d, 0x7b, - 0x31, 0x2c, 0x38, 0x7d, 0x28, 0x2d, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, - 0x5d, 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x29, 0x2a, 0x24, 0x2a, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, - 0x52, 0x10, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, - 0x65, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x15, 0xba, 0x48, 0x12, 0x72, 0x10, 0x10, 0x01, 0x32, 0x0c, 0x5e, 0x5c, 0x53, 0x2b, - 0x28, 0x20, 0x5c, 0x53, 0x2b, 0x29, 0x2a, 0x24, 0x48, 0x04, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, - 0x65, 0x88, 0x01, 0x01, 0x12, 0x28, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x73, - 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x42, 0x0c, 0xba, 0x48, 0x09, 0x92, 0x01, 0x06, 0x22, 0x04, - 0x72, 0x02, 0x60, 0x01, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x73, 0x12, 0x26, - 0x0a, 0x07, 0x74, 0x6f, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x48, 0x05, 0x52, 0x06, 0x74, 0x6f, 0x73, - 0x55, 0x72, 0x69, 0x88, 0x01, 0x01, 0x12, 0x98, 0x01, 0x0a, 0x11, 0x74, 0x6f, 0x73, 0x5f, 0x75, - 0x72, 0x69, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x0e, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, - 0x31, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x2e, 0x54, 0x6f, 0x73, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, - 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x36, 0xba, 0x48, 0x33, 0x9a, 0x01, 0x30, - 0x22, 0x27, 0x72, 0x25, 0x32, 0x23, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x5d, 0x7b, - 0x31, 0x2c, 0x38, 0x7d, 0x28, 0x2d, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, - 0x5d, 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x29, 0x2a, 0x24, 0x2a, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, - 0x52, 0x0f, 0x74, 0x6f, 0x73, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, - 0x64, 0x12, 0x2c, 0x0a, 0x0a, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x75, 0x72, 0x69, 0x18, - 0x0f, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x48, - 0x06, 0x52, 0x09, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x72, 0x69, 0x88, 0x01, 0x01, 0x12, - 0xa1, 0x01, 0x0a, 0x14, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x75, 0x72, 0x69, 0x5f, 0x6c, - 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, - 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, - 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x36, 0xba, 0x48, 0x33, 0x9a, 0x01, 0x30, 0x22, - 0x27, 0x72, 0x25, 0x32, 0x23, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x5d, 0x7b, 0x31, - 0x2c, 0x38, 0x7d, 0x28, 0x2d, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5d, - 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x29, 0x2a, 0x24, 0x2a, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x52, - 0x12, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, - 0x7a, 0x65, 0x64, 0x12, 0x28, 0x0a, 0x08, 0x6a, 0x77, 0x6b, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x18, - 0x11, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x48, - 0x07, 0x52, 0x07, 0x6a, 0x77, 0x6b, 0x73, 0x55, 0x72, 0x69, 0x88, 0x01, 0x01, 0x12, 0x37, 0x0a, - 0x04, 0x6a, 0x77, 0x6b, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, 0x65, + 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4d, 0x0a, 0x0d, + 0x4a, 0x73, 0x6f, 0x6e, 0x57, 0x65, 0x62, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x74, 0x12, 0x3c, 0x0a, + 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x2e, 0x4a, 0x73, - 0x6f, 0x6e, 0x57, 0x65, 0x62, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x74, 0x48, 0x08, 0x52, 0x04, 0x6a, - 0x77, 0x6b, 0x73, 0x88, 0x01, 0x01, 0x12, 0x30, 0x0a, 0x0b, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, - 0x72, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, - 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x48, 0x09, 0x52, 0x0a, 0x73, 0x6f, 0x66, 0x74, 0x77, - 0x61, 0x72, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x3a, 0x0a, 0x10, 0x73, 0x6f, 0x66, 0x74, - 0x77, 0x61, 0x72, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x14, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x48, 0x0a, - 0x52, 0x0f, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x88, 0x01, 0x01, 0x1a, 0x46, 0x0a, 0x18, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4e, 0x61, - 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x45, 0x0a, 0x17, - 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, + 0x6f, 0x6e, 0x57, 0x65, 0x62, 0x4b, 0x65, 0x79, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xc8, 0x01, 0x01, + 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22, 0xcf, 0x07, 0x0a, 0x0a, + 0x4a, 0x73, 0x6f, 0x6e, 0x57, 0x65, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x03, 0x6b, 0x74, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1d, 0xba, 0x48, 0x1a, 0xc8, 0x01, 0x01, 0x72, + 0x15, 0x10, 0x01, 0x52, 0x03, 0x52, 0x53, 0x41, 0x52, 0x02, 0x45, 0x43, 0x52, 0x03, 0x6f, 0x63, + 0x74, 0x52, 0x03, 0x4f, 0x4b, 0x50, 0x52, 0x03, 0x6b, 0x74, 0x79, 0x12, 0x26, 0x0a, 0x03, 0x75, + 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0f, 0xba, 0x48, 0x0c, 0x72, 0x0a, 0x52, + 0x03, 0x73, 0x69, 0x67, 0x52, 0x03, 0x65, 0x6e, 0x63, 0x48, 0x01, 0x52, 0x03, 0x75, 0x73, 0x65, + 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6f, 0x70, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x09, 0x42, 0x0c, 0xba, 0x48, 0x09, 0x92, 0x01, 0x06, 0x22, 0x04, 0x72, 0x02, + 0x10, 0x01, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4f, 0x70, 0x73, 0x12, 0x1e, 0x0a, 0x03, 0x61, 0x6c, + 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, + 0x48, 0x02, 0x52, 0x03, 0x61, 0x6c, 0x67, 0x88, 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x03, 0x6b, 0x69, + 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, + 0x48, 0x03, 0x52, 0x03, 0x6b, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x42, 0x0a, 0x0a, 0x72, 0x73, + 0x61, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, + 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x73, 0x61, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x73, 0x48, 0x00, 0x52, 0x09, 0x72, 0x73, 0x61, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x3f, + 0x0a, 0x09, 0x65, 0x63, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x20, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, + 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x63, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x08, 0x65, 0x63, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, + 0x54, 0x0a, 0x10, 0x73, 0x79, 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x70, 0x61, 0x72, + 0x61, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x69, 0x65, 0x74, 0x66, + 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6d, 0x6d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x73, 0x48, 0x00, 0x52, 0x0f, 0x73, 0x79, 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x42, 0x0a, 0x0a, 0x6f, 0x6b, 0x70, 0x5f, 0x70, 0x61, 0x72, + 0x61, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x69, 0x65, 0x74, 0x66, + 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6b, 0x70, 0x4b, + 0x65, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x09, + 0x6f, 0x6b, 0x70, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x3a, 0xab, 0x03, 0xba, 0x48, 0xa7, 0x03, + 0x1a, 0x66, 0x0a, 0x12, 0x6a, 0x77, 0x6b, 0x2e, 0x6b, 0x74, 0x79, 0x5f, 0x70, 0x61, 0x72, 0x61, + 0x6d, 0x73, 0x2e, 0x72, 0x73, 0x61, 0x12, 0x25, 0x72, 0x73, 0x61, 0x5f, 0x70, 0x61, 0x72, 0x61, + 0x6d, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x6b, 0x74, 0x79, 0x20, 0x27, 0x52, 0x53, 0x41, 0x27, 0x1a, 0x29, 0x74, + 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x74, 0x79, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x52, 0x53, 0x41, 0x27, + 0x20, 0x7c, 0x7c, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x72, 0x73, 0x61, + 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x29, 0x1a, 0x61, 0x0a, 0x11, 0x6a, 0x77, 0x6b, 0x2e, + 0x6b, 0x74, 0x79, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x65, 0x63, 0x12, 0x23, 0x65, + 0x63, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x65, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6b, 0x74, 0x79, 0x20, 0x27, 0x45, + 0x43, 0x27, 0x1a, 0x27, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x74, 0x79, 0x20, 0x21, 0x3d, 0x20, + 0x27, 0x45, 0x43, 0x27, 0x20, 0x7c, 0x7c, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, + 0x2e, 0x65, 0x63, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x29, 0x1a, 0x72, 0x0a, 0x12, 0x6a, + 0x77, 0x6b, 0x2e, 0x6b, 0x74, 0x79, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x6f, 0x63, + 0x74, 0x12, 0x2b, 0x73, 0x79, 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x70, 0x61, 0x72, + 0x61, 0x6d, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, + 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6b, 0x74, 0x79, 0x20, 0x27, 0x6f, 0x63, 0x74, 0x27, 0x1a, 0x2f, + 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x74, 0x79, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x6f, 0x63, 0x74, + 0x27, 0x20, 0x7c, 0x7c, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x73, 0x79, + 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x29, 0x1a, + 0x66, 0x0a, 0x12, 0x6a, 0x77, 0x6b, 0x2e, 0x6b, 0x74, 0x79, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, + 0x73, 0x2e, 0x6f, 0x6b, 0x70, 0x12, 0x25, 0x6f, 0x6b, 0x70, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, + 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x6b, 0x74, 0x79, 0x20, 0x27, 0x4f, 0x4b, 0x50, 0x27, 0x1a, 0x29, 0x74, 0x68, + 0x69, 0x73, 0x2e, 0x6b, 0x74, 0x79, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x4f, 0x4b, 0x50, 0x27, 0x20, + 0x7c, 0x7c, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6f, 0x6b, 0x70, 0x5f, + 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x29, 0x42, 0x1c, 0x0a, 0x13, 0x6b, 0x65, 0x79, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x05, + 0xba, 0x48, 0x02, 0x08, 0x00, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x75, 0x73, 0x65, 0x42, 0x06, 0x0a, + 0x04, 0x5f, 0x61, 0x6c, 0x67, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x6b, 0x69, 0x64, 0x22, 0x46, 0x0a, + 0x10, 0x52, 0x73, 0x61, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x73, 0x12, 0x18, 0x0a, 0x01, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, + 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x01, 0x6e, 0x12, 0x18, 0x0a, 0x01, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, + 0x10, 0x01, 0x52, 0x01, 0x65, 0x22, 0x76, 0x0a, 0x0f, 0x45, 0x63, 0x4b, 0x65, 0x79, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2f, 0x0a, 0x03, 0x63, 0x72, 0x76, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1d, 0xba, 0x48, 0x1a, 0xc8, 0x01, 0x01, 0x72, 0x15, 0x52, + 0x05, 0x50, 0x2d, 0x32, 0x35, 0x36, 0x52, 0x05, 0x50, 0x2d, 0x33, 0x38, 0x34, 0x52, 0x05, 0x50, + 0x2d, 0x35, 0x32, 0x31, 0x52, 0x03, 0x63, 0x72, 0x76, 0x12, 0x18, 0x0a, 0x01, 0x78, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, + 0x52, 0x01, 0x78, 0x12, 0x18, 0x0a, 0x01, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, + 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x01, 0x79, 0x22, 0x32, 0x0a, + 0x16, 0x53, 0x79, 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x01, 0x6b, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x01, + 0x6b, 0x22, 0x7e, 0x0a, 0x10, 0x4f, 0x6b, 0x70, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, + 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x38, 0x0a, 0x03, 0x63, 0x72, 0x76, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x26, 0xba, 0x48, 0x23, 0xc8, 0x01, 0x01, 0x72, 0x1e, 0x52, 0x07, 0x45, 0x64, + 0x32, 0x35, 0x35, 0x31, 0x39, 0x52, 0x05, 0x45, 0x64, 0x34, 0x34, 0x38, 0x52, 0x06, 0x58, 0x32, + 0x35, 0x35, 0x31, 0x39, 0x52, 0x04, 0x58, 0x34, 0x34, 0x38, 0x52, 0x03, 0x63, 0x72, 0x76, 0x12, + 0x30, 0x0a, 0x01, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x22, 0xba, 0x48, 0x1f, 0xc8, + 0x01, 0x01, 0x72, 0x1a, 0x10, 0x01, 0x32, 0x16, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, + 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x3d, 0x7b, 0x30, 0x2c, 0x32, 0x7d, 0x24, 0x52, 0x01, + 0x78, 0x22, 0xa5, 0x11, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x36, + 0x0a, 0x0d, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x11, 0xba, 0x48, 0x0e, 0x92, 0x01, 0x0b, 0x08, 0x01, 0x22, + 0x07, 0x72, 0x05, 0x10, 0x01, 0x88, 0x01, 0x01, 0x52, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, + 0x63, 0x74, 0x55, 0x72, 0x69, 0x73, 0x12, 0x76, 0x0a, 0x1a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, + 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x34, 0xba, 0x48, 0x31, 0x72, + 0x2f, 0x52, 0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, + 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x73, 0x74, 0x52, 0x13, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f, 0x62, 0x61, 0x73, 0x69, 0x63, + 0x48, 0x00, 0x52, 0x17, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1f, + 0x0a, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, + 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, + 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x48, 0x01, 0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0xa0, 0x01, 0x0a, 0x15, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, + 0x65, 0x64, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, + 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x4c, 0x6f, + 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x38, 0xba, 0x48, + 0x35, 0x9a, 0x01, 0x32, 0x22, 0x27, 0x72, 0x25, 0x32, 0x23, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, + 0x2d, 0x5a, 0x5d, 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x28, 0x2d, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, + 0x5a, 0x30, 0x2d, 0x39, 0x5d, 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x29, 0x2a, 0x24, 0x2a, 0x07, 0x72, + 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, 0x13, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4e, 0x61, + 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x0a, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x48, 0x02, 0x52, 0x09, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x55, 0x72, 0x69, 0x88, 0x01, 0x01, 0x12, 0x9b, 0x01, 0x0a, 0x14, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, + 0x65, 0x64, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, + 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, + 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x36, 0xba, 0x48, 0x33, + 0x9a, 0x01, 0x30, 0x22, 0x27, 0x72, 0x25, 0x32, 0x23, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, + 0x5a, 0x5d, 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x28, 0x2d, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, + 0x30, 0x2d, 0x39, 0x5d, 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x29, 0x2a, 0x24, 0x2a, 0x05, 0x72, 0x03, + 0x88, 0x01, 0x01, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x69, 0x4c, 0x6f, + 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x28, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, + 0x75, 0x72, 0x69, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, + 0x88, 0x01, 0x01, 0x48, 0x03, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x69, 0x88, 0x01, + 0x01, 0x12, 0x95, 0x01, 0x0a, 0x12, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x69, 0x5f, 0x6c, + 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, + 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, + 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x55, 0x72, + 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, + 0x36, 0xba, 0x48, 0x33, 0x9a, 0x01, 0x30, 0x22, 0x27, 0x72, 0x25, 0x32, 0x23, 0x5e, 0x5b, 0x61, + 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x5d, 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x28, 0x2d, 0x5b, 0x61, 0x2d, + 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5d, 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x29, 0x2a, 0x24, + 0x2a, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x52, 0x10, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x69, + 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x63, 0x6f, + 0x70, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x42, 0x15, 0xba, 0x48, 0x12, 0x72, 0x10, 0x10, + 0x01, 0x32, 0x0c, 0x5e, 0x5c, 0x53, 0x2b, 0x28, 0x20, 0x5c, 0x53, 0x2b, 0x29, 0x2a, 0x24, 0x48, + 0x04, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x28, 0x0a, 0x08, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x42, 0x0c, 0xba, + 0x48, 0x09, 0x92, 0x01, 0x06, 0x22, 0x04, 0x72, 0x02, 0x60, 0x01, 0x52, 0x08, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x63, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x07, 0x74, 0x6f, 0x73, 0x5f, 0x75, 0x72, 0x69, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, + 0x48, 0x05, 0x52, 0x06, 0x74, 0x6f, 0x73, 0x55, 0x72, 0x69, 0x88, 0x01, 0x01, 0x12, 0x92, 0x01, + 0x0a, 0x11, 0x74, 0x6f, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x69, + 0x7a, 0x65, 0x64, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x69, 0x65, 0x74, 0x66, + 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x6f, 0x73, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, + 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x36, 0xba, 0x48, 0x33, 0x9a, 0x01, + 0x30, 0x22, 0x27, 0x72, 0x25, 0x32, 0x23, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x5d, + 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x28, 0x2d, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, + 0x39, 0x5d, 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x29, 0x2a, 0x24, 0x2a, 0x05, 0x72, 0x03, 0x88, 0x01, + 0x01, 0x52, 0x0f, 0x74, 0x6f, 0x73, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, + 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x0a, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x75, 0x72, 0x69, + 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, + 0x48, 0x06, 0x52, 0x09, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x72, 0x69, 0x88, 0x01, 0x01, + 0x12, 0x9b, 0x01, 0x0a, 0x14, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x75, 0x72, 0x69, 0x5f, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x31, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, + 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x42, 0x36, 0xba, 0x48, 0x33, 0x9a, 0x01, 0x30, 0x22, 0x27, 0x72, 0x25, 0x32, 0x23, + 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x5d, 0x7b, 0x31, 0x2c, 0x38, 0x7d, 0x28, 0x2d, + 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5d, 0x7b, 0x31, 0x2c, 0x38, 0x7d, + 0x29, 0x2a, 0x24, 0x2a, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x52, 0x12, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x28, + 0x0a, 0x08, 0x6a, 0x77, 0x6b, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x48, 0x07, 0x52, 0x07, 0x6a, 0x77, + 0x6b, 0x73, 0x55, 0x72, 0x69, 0x88, 0x01, 0x01, 0x12, 0x37, 0x0a, 0x04, 0x6a, 0x77, 0x6b, 0x73, + 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, + 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, 0x57, 0x65, 0x62, + 0x4b, 0x65, 0x79, 0x53, 0x65, 0x74, 0x48, 0x08, 0x52, 0x04, 0x6a, 0x77, 0x6b, 0x73, 0x88, 0x01, + 0x01, 0x12, 0x30, 0x0a, 0x0b, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, + 0xff, 0x01, 0x48, 0x09, 0x52, 0x0a, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x49, 0x64, + 0x88, 0x01, 0x01, 0x12, 0x3a, 0x0a, 0x10, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x5f, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, + 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x48, 0x0a, 0x52, 0x0f, 0x73, 0x6f, 0x66, + 0x74, 0x77, 0x61, 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x1a, + 0x46, 0x0a, 0x18, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x4c, 0x6f, 0x63, + 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x45, 0x0a, 0x17, 0x43, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x43, + 0x0a, 0x15, 0x4c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x1a, 0x43, 0x0a, 0x15, 0x4c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x69, 0x4c, 0x6f, - 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x42, 0x0a, 0x14, 0x54, 0x6f, 0x73, 0x55, - 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x45, 0x0a, 0x17, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, - 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x3a, 0x7e, 0xba, 0x48, 0x7b, 0x1a, 0x79, 0x0a, 0x25, 0x63, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x6a, 0x77, 0x6b, 0x73, - 0x5f, 0x6d, 0x75, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x28, 0x6a, 0x77, 0x6b, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x20, 0x61, 0x6e, 0x64, 0x20, - 0x6a, 0x77, 0x6b, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6d, 0x75, 0x74, 0x75, 0x61, 0x6c, 0x6c, - 0x79, 0x20, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x1a, 0x26, 0x21, 0x68, 0x61, - 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6a, 0x77, 0x6b, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x29, - 0x20, 0x7c, 0x7c, 0x20, 0x21, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6a, 0x77, - 0x6b, 0x73, 0x29, 0x42, 0x1d, 0x0a, 0x1b, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x65, 0x6e, - 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x72, - 0x69, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x69, 0x42, 0x08, - 0x0a, 0x06, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x74, 0x6f, 0x73, - 0x5f, 0x75, 0x72, 0x69, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, - 0x75, 0x72, 0x69, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6a, 0x77, 0x6b, 0x73, 0x5f, 0x75, 0x72, 0x69, - 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6a, 0x77, 0x6b, 0x73, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x73, 0x6f, - 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x73, 0x6f, - 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0xba, - 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, - 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, - 0x75, 0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x72, 0x66, 0x63, 0x37, - 0x35, 0x39, 0x31, 0x76, 0x31, 0x3b, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x76, 0x31, 0xa2, - 0x02, 0x03, 0x49, 0x52, 0x58, 0xaa, 0x02, 0x0f, 0x49, 0x65, 0x74, 0x66, 0x2e, 0x52, 0x66, 0x63, - 0x37, 0x35, 0x39, 0x31, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0f, 0x49, 0x65, 0x74, 0x66, 0x5c, 0x52, - 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x1b, 0x49, 0x65, 0x74, 0x66, - 0x5c, 0x52, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x11, 0x49, 0x65, 0x74, 0x66, 0x3a, 0x3a, - 0x52, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x02, 0x38, 0x01, 0x1a, 0x42, 0x0a, 0x14, 0x54, 0x6f, 0x73, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, + 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x45, 0x0a, 0x17, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x55, 0x72, 0x69, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x3a, 0x7e, + 0xba, 0x48, 0x7b, 0x1a, 0x79, 0x0a, 0x25, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x6a, 0x77, 0x6b, 0x73, 0x5f, 0x6d, 0x75, 0x74, 0x75, + 0x61, 0x6c, 0x5f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x6a, 0x77, + 0x6b, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6a, 0x77, 0x6b, 0x73, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x6d, 0x75, 0x74, 0x75, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x65, 0x78, 0x63, + 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x1a, 0x26, 0x21, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, + 0x73, 0x2e, 0x6a, 0x77, 0x6b, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x21, + 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6a, 0x77, 0x6b, 0x73, 0x29, 0x42, 0x1d, + 0x0a, 0x1b, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x42, 0x0e, 0x0a, + 0x0c, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0d, 0x0a, + 0x0b, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x42, 0x0b, 0x0a, 0x09, + 0x5f, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x69, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x74, 0x6f, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x42, + 0x0d, 0x0a, 0x0b, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x75, 0x72, 0x69, 0x42, 0x0b, + 0x0a, 0x09, 0x5f, 0x6a, 0x77, 0x6b, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x42, 0x07, 0x0a, 0x05, 0x5f, + 0x6a, 0x77, 0x6b, 0x73, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, + 0x65, 0x5f, 0x69, 0x64, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, + 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xc2, 0x01, 0x0a, 0x0c, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x20, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, + 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3e, 0x0a, 0x0a, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x00, 0x52, 0x09, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x88, 0x01, 0x01, 0x12, 0x41, 0x0a, 0x0a, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x06, 0xba, 0x48, + 0x03, 0xc8, 0x01, 0x01, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x42, + 0x0d, 0x0a, 0x0b, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x22, 0xf6, + 0x01, 0x0a, 0x12, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4c, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, + 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, + 0x01, 0x01, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x4e, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, + 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, + 0x01, 0x52, 0x10, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x42, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x65, 0x74, + 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x42, 0xba, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, + 0x69, 0x65, 0x74, 0x66, 0x2e, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x76, 0x31, 0x42, + 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x39, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, + 0x75, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x72, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x76, 0x31, 0x3b, 0x72, + 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x49, 0x52, 0x58, 0xaa, 0x02, + 0x0f, 0x49, 0x65, 0x74, 0x66, 0x2e, 0x52, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x2e, 0x56, 0x31, + 0xca, 0x02, 0x0f, 0x49, 0x65, 0x74, 0x66, 0x5c, 0x52, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, 0x5c, + 0x56, 0x31, 0xe2, 0x02, 0x1b, 0x49, 0x65, 0x74, 0x66, 0x5c, 0x52, 0x66, 0x63, 0x37, 0x35, 0x39, + 0x31, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0xea, 0x02, 0x11, 0x49, 0x65, 0x74, 0x66, 0x3a, 0x3a, 0x52, 0x66, 0x63, 0x37, 0x35, 0x39, 0x31, + 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -972,7 +1113,7 @@ func file_types_proto_rawDescGZIP() []byte { return file_types_proto_rawDescData } -var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 14) var file_types_proto_goTypes = []any{ (*JsonWebKeySet)(nil), // 0: ietf.rfc7591.v1.JsonWebKeySet (*JsonWebKey)(nil), // 1: ietf.rfc7591.v1.JsonWebKey @@ -980,12 +1121,15 @@ var file_types_proto_goTypes = []any{ (*EcKeyParameters)(nil), // 3: ietf.rfc7591.v1.EcKeyParameters (*SymmetricKeyParameters)(nil), // 4: ietf.rfc7591.v1.SymmetricKeyParameters (*OkpKeyParameters)(nil), // 5: ietf.rfc7591.v1.OkpKeyParameters - (*ClientMetadata)(nil), // 6: ietf.rfc7591.v1.ClientMetadata - nil, // 7: ietf.rfc7591.v1.ClientMetadata.ClientNameLocalizedEntry - nil, // 8: ietf.rfc7591.v1.ClientMetadata.ClientUriLocalizedEntry - nil, // 9: ietf.rfc7591.v1.ClientMetadata.LogoUriLocalizedEntry - nil, // 10: ietf.rfc7591.v1.ClientMetadata.TosUriLocalizedEntry - nil, // 11: ietf.rfc7591.v1.ClientMetadata.PolicyUriLocalizedEntry + (*Metadata)(nil), // 6: ietf.rfc7591.v1.Metadata + (*ClientSecret)(nil), // 7: ietf.rfc7591.v1.ClientSecret + (*ClientRegistration)(nil), // 8: ietf.rfc7591.v1.ClientRegistration + nil, // 9: ietf.rfc7591.v1.Metadata.ClientNameLocalizedEntry + nil, // 10: ietf.rfc7591.v1.Metadata.ClientUriLocalizedEntry + nil, // 11: ietf.rfc7591.v1.Metadata.LogoUriLocalizedEntry + nil, // 12: ietf.rfc7591.v1.Metadata.TosUriLocalizedEntry + nil, // 13: ietf.rfc7591.v1.Metadata.PolicyUriLocalizedEntry + (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp } var file_types_proto_depIdxs = []int32{ 1, // 0: ietf.rfc7591.v1.JsonWebKeySet.keys:type_name -> ietf.rfc7591.v1.JsonWebKey @@ -993,17 +1137,22 @@ var file_types_proto_depIdxs = []int32{ 3, // 2: ietf.rfc7591.v1.JsonWebKey.ec_params:type_name -> ietf.rfc7591.v1.EcKeyParameters 4, // 3: ietf.rfc7591.v1.JsonWebKey.symmetric_params:type_name -> ietf.rfc7591.v1.SymmetricKeyParameters 5, // 4: ietf.rfc7591.v1.JsonWebKey.okp_params:type_name -> ietf.rfc7591.v1.OkpKeyParameters - 7, // 5: ietf.rfc7591.v1.ClientMetadata.client_name_localized:type_name -> ietf.rfc7591.v1.ClientMetadata.ClientNameLocalizedEntry - 8, // 6: ietf.rfc7591.v1.ClientMetadata.client_uri_localized:type_name -> ietf.rfc7591.v1.ClientMetadata.ClientUriLocalizedEntry - 9, // 7: ietf.rfc7591.v1.ClientMetadata.logo_uri_localized:type_name -> ietf.rfc7591.v1.ClientMetadata.LogoUriLocalizedEntry - 10, // 8: ietf.rfc7591.v1.ClientMetadata.tos_uri_localized:type_name -> ietf.rfc7591.v1.ClientMetadata.TosUriLocalizedEntry - 11, // 9: ietf.rfc7591.v1.ClientMetadata.policy_uri_localized:type_name -> ietf.rfc7591.v1.ClientMetadata.PolicyUriLocalizedEntry - 0, // 10: ietf.rfc7591.v1.ClientMetadata.jwks:type_name -> ietf.rfc7591.v1.JsonWebKeySet - 11, // [11:11] is the sub-list for method output_type - 11, // [11:11] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 9, // 5: ietf.rfc7591.v1.Metadata.client_name_localized:type_name -> ietf.rfc7591.v1.Metadata.ClientNameLocalizedEntry + 10, // 6: ietf.rfc7591.v1.Metadata.client_uri_localized:type_name -> ietf.rfc7591.v1.Metadata.ClientUriLocalizedEntry + 11, // 7: ietf.rfc7591.v1.Metadata.logo_uri_localized:type_name -> ietf.rfc7591.v1.Metadata.LogoUriLocalizedEntry + 12, // 8: ietf.rfc7591.v1.Metadata.tos_uri_localized:type_name -> ietf.rfc7591.v1.Metadata.TosUriLocalizedEntry + 13, // 9: ietf.rfc7591.v1.Metadata.policy_uri_localized:type_name -> ietf.rfc7591.v1.Metadata.PolicyUriLocalizedEntry + 0, // 10: ietf.rfc7591.v1.Metadata.jwks:type_name -> ietf.rfc7591.v1.JsonWebKeySet + 14, // 11: ietf.rfc7591.v1.ClientSecret.expires_at:type_name -> google.protobuf.Timestamp + 14, // 12: ietf.rfc7591.v1.ClientSecret.created_at:type_name -> google.protobuf.Timestamp + 6, // 13: ietf.rfc7591.v1.ClientRegistration.request_metadata:type_name -> ietf.rfc7591.v1.Metadata + 6, // 14: ietf.rfc7591.v1.ClientRegistration.response_metadata:type_name -> ietf.rfc7591.v1.Metadata + 7, // 15: ietf.rfc7591.v1.ClientRegistration.client_secret:type_name -> ietf.rfc7591.v1.ClientSecret + 16, // [16:16] is the sub-list for method output_type + 16, // [16:16] is the sub-list for method input_type + 16, // [16:16] is the sub-list for extension type_name + 16, // [16:16] is the sub-list for extension extendee + 0, // [0:16] is the sub-list for field type_name } func init() { file_types_proto_init() } @@ -1018,13 +1167,14 @@ func file_types_proto_init() { (*JsonWebKey_OkpParams)(nil), } file_types_proto_msgTypes[6].OneofWrappers = []any{} + file_types_proto_msgTypes[7].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_types_proto_rawDesc), len(file_types_proto_rawDesc)), NumEnums: 0, - NumMessages: 12, + NumMessages: 14, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/rfc7591/types.proto b/internal/rfc7591/types.proto index 1dc8607cf..e73db2ec1 100644 --- a/internal/rfc7591/types.proto +++ b/internal/rfc7591/types.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package ietf.rfc7591.v1; import "buf/validate/validate.proto"; +import "google/protobuf/timestamp.proto"; option go_package = "github.com/pomerium/pomerium/internal/rfc7591"; @@ -153,7 +154,7 @@ message OkpKeyParameters { // Represents the client metadata fields defined in RFC 7591 Section 2. // These values are used both as input to registration requests and output in // registration responses. -message ClientMetadata { +message Metadata { // Array of redirection URI strings. REQUIRED for clients using flows with // redirection. repeated string redirect_uris = 1 [(buf.validate.field).repeated = { @@ -169,23 +170,11 @@ message ClientMetadata { // OPTIONAL. Array of OAuth 2.0 grant type strings that the client can use. // If omitted, defaults to ["authorization_code"]. - repeated string grant_types = 3 [(buf.validate.field).repeated.items.string = { - in: [ - "authorization_code", - "implicit", - "password", - "client_credentials", - "refresh_token", - "urn:ietf:params:oauth:grant-type:jwt-bearer", - "urn:ietf:params:oauth:grant-type:saml2-bearer" - ], - }]; + repeated string grant_types = 3; // OPTIONAL. Array of the OAuth 2.0 response type strings that the client can // use. If omitted, defaults to ["code"]. - repeated string response_types = 4 [(buf.validate.field).repeated.items.string = { - in: ["code", "token"], - }]; + repeated string response_types = 4; // OPTIONAL. Human-readable string name of the client. RECOMMENDED. optional string client_name = 5 [(buf.validate.field).string = {min_len: 1, max_len: 255}]; @@ -266,3 +255,32 @@ message ClientMetadata { message: "jwks_uri and jwks are mutually exclusive", }; } + +message ClientSecret { + // REQUIRED. The client secret value. + string value = 1 [ + (buf.validate.field).required = true, + (buf.validate.field).string.min_len = 1 + ]; + // OPTIONAL. The expiration time of the client secret. + optional google.protobuf.Timestamp expires_at = 2; + google.protobuf.Timestamp created_at = 3 [ + (buf.validate.field).required = true + ]; +} + +// Represents the client registration storage structure. +message ClientRegistration { + // Contains the client metadata as requested by the client. + Metadata request_metadata = 1 [ + (buf.validate.field).required = true + ]; + + // Contains the client metadata as was returned by the server. + Metadata response_metadata = 2 [ + (buf.validate.field).required = true + ]; + + // OPTIONAL. The "client_secret" parameter is the secret used by the client + ClientSecret client_secret = 3; +} diff --git a/internal/rfc7591/types_test.go b/internal/rfc7591/types_test.go deleted file mode 100644 index 3096ae34f..000000000 --- a/internal/rfc7591/types_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package rfc7591v1_test - -import ( - "testing" - - "github.com/bufbuild/protovalidate-go" - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/testing/protocmp" - - rfc7591 "github.com/pomerium/pomerium/internal/rfc7591" -) - -func TestValidation(t *testing.T) { - v := &rfc7591.JsonWebKey{Kty: "Invalid"} - require.ErrorContains(t, protovalidate.Validate(v), `kty: value must be in list [RSA, EC, oct, OKP] [string.in]`) -} - -func TestJSONMarshal(t *testing.T) { - data := ` -{ - "redirect_uris": [ - "http://localhost:8002/oauth/callback" - ], - "token_endpoint_auth_method": "none", - "grant_types": [ - "authorization_code", - "refresh_token" - ], - "response_types": [ - "code" - ], - "client_name": "MCP Inspector", - "client_uri": "https://github.com/modelcontextprotocol/inspector" -}` - v := &rfc7591.ClientMetadata{} - require.NoError(t, protojson.Unmarshal([]byte(data), v)) - diff := cmp.Diff(&rfc7591.ClientMetadata{ - RedirectUris: []string{"http://localhost:8002/oauth/callback"}, - TokenEndpointAuthMethod: proto.String("none"), - GrantTypes: []string{"authorization_code", "refresh_token"}, - ResponseTypes: []string{"code"}, - ClientName: proto.String("MCP Inspector"), - ClientUri: proto.String("https://github.com/modelcontextprotocol/inspector"), - }, v, protocmp.Transform()) - require.Empty(t, diff) -}