mcp: client registration/token fixes (#5649)

## Summary

Fixes to MCP code registration and token requests. 

1. ease some requirements on fields that are RECOMMENDED 
2. fill in defaults
3. store both request and response in the client registration
4. check client secret in the /token request

## Related issues

- Fixes
https://linear.app/pomerium/issue/ENG-2462/mcp-ignore-unknown-grant-types-in-the-client-registration
- Fixes
https://linear.app/pomerium/issue/ENG-2461/mcp-support-client-secret-in-dynamic-client-registration
 
## User Explanation

<!-- How would you explain this change to the user? If this
change doesn't create any user-facing changes, you can leave
this blank. If filled out, add the `docs` label -->

## Checklist

- [x] reference any related issues
- [x] updated unit tests
- [x] add appropriate label (`enhancement`, `bug`, `breaking`,
`dependencies`, `ci`)
- [ ] ready for review
This commit is contained in:
Denis Mishin 2025-06-11 08:28:24 -07:00 committed by GitHub
parent 200f2e8164
commit 777b3b12d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 1440 additions and 461 deletions

View file

@ -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)

View file

@ -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"},

View file

@ -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
}

View file

@ -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
}

View file

@ -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,

View file

@ -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)

View file

@ -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"),
}

View file

@ -2,5 +2,5 @@
version: v2
deps:
- name: buf.build/bufbuild/protovalidate
commit: 8976f5be98c146529b1cc15cd2012b60
digest: b5:5d513af91a439d9e78cacac0c9455c7cb885a8737d30405d0b91974fe05276d19c07a876a51a107213a3d01b83ecc912996cdad4cddf7231f91379079cf7488d
commit: b52ab10f44684cb19d1fbcad56aedd36
digest: b5:5f464399f5ea7546eb3c6a4e822a306da538298f3d87e9f974f4523d4955de396eb2b8b52a2f3f06ac290764d80cecbaa0a4c96560558e43d9b1c722e61a9d5c

View file

@ -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 (

View file

@ -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

View file

@ -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)

View file

@ -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)
}

View file

@ -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 {

View file

@ -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"),

View file

@ -2,5 +2,5 @@
version: v2
deps:
- name: buf.build/bufbuild/protovalidate
commit: 7712fb530c574b95bc1d57c0877543c3
digest: b5:b3e9c9428384357e3b73e4d5a4614328b0a4b1595b10163bbe9483fa16204749274c41797bd49b0d716479c855aa35c1172a94f471fa120ba8369637fd138829
commit: b52ab10f44684cb19d1fbcad56aedd36
digest: b5:5f464399f5ea7546eb3c6a4e822a306da538298f3d87e9f974f4523d4955de396eb2b8b52a2f3f06ac290764d80cecbaa0a4c96560558e43d9b1c722e61a9d5c

103
internal/rfc7591/format.go Normal file
View file

@ -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)
}

View file

@ -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"])
}
})
}

View file

@ -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,
},

View file

@ -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;
}

View file

@ -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)
}