From 4fc1acb4de6cc0059541fcbc51e1d1f5b2d7f8b7 Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Thu, 21 May 2026 09:07:31 -0700 Subject: [PATCH 1/7] feat(authorization): make v2 request limits configurable Signed-off-by: jakedoublev --- docs/Configuring.md | 11 + docs/grpc/index.html | 6 +- .../v2/authorization.openapi.yaml | 32 +- .../v2/entity_resolution.openapi.yaml | 6 +- .../go/authorization/v2/authorization.pb.go | 531 +++++++++--------- service/authorization/v2/authorization.go | 12 + service/authorization/v2/authorization.proto | 24 +- .../authorization/v2/authorization_test.go | 105 ++-- service/authorization/v2/config.go | 42 +- service/authorization/v2/config_test.go | 108 +++- service/authorization/v2/validation.go | 91 +++ service/authorization/v2/validation_test.go | 284 ++++++++++ 12 files changed, 847 insertions(+), 405 deletions(-) create mode 100644 service/authorization/v2/validation.go create mode 100644 service/authorization/v2/validation_test.go diff --git a/docs/Configuring.md b/docs/Configuring.md index c99fb0e24c..2dbb4f30c0 100644 --- a/docs/Configuring.md +++ b/docs/Configuring.md @@ -445,6 +445,11 @@ Root level key `authorization` | ------------------------------------------- | -------------------------------------------------------------- | ------- | --------------------- | | `entitlement_policy_cache.enabled` | Enable the entitlement policy cache | `false` | | | `entitlement_policy_cache.refresh_interval` | How often to refresh the entitlement policy cache (e.g. `30s`) | | | +| `request_limits.resource_attribute_values_max` | Maximum attribute value FQNs allowed in `resource.attribute_values.fqns` | `20` | | +| `request_limits.entity_chain_entities_max` | Maximum entities allowed in `entity_identifier.entity_chain.entities` | `10` | | +| `request_limits.fulfillable_obligation_fqns_max` | Maximum fulfillable obligation FQNs allowed per decision request | `50` | | +| `request_limits.multi_resource_request_max` | Maximum resources allowed in `GetDecisionMultiResourceRequest.resources` | `1000` | | +| `request_limits.bulk_decision_request_max` | Maximum decision requests allowed in `GetDecisionBulkRequest.decision_requests` | `200` | | #### Example: Authorization v1 @@ -464,6 +469,12 @@ services: entitlement_policy_cache: enabled: false refresh_interval: 30s + request_limits: + resource_attribute_values_max: 20 + entity_chain_entities_max: 10 + fulfillable_obligation_fqns_max: 50 + multi_resource_request_max: 1000 + bulk_decision_request_max: 200 ``` ### Entity Resolution diff --git a/docs/grpc/index.html b/docs/grpc/index.html index bb9f78bdc8..fe41cd47eb 100644 --- a/docs/grpc/index.html +++ b/docs/grpc/index.html @@ -5316,7 +5316,7 @@

EntityIdentifier

entity_chain entity.EntityChain -

chain of one or more entities and at most 10

+

chain of one or more entities

@@ -5397,7 +5397,7 @@

GetDecisionBulkResponse

GetDecisionMultiResourceRequest

-

Can the identified entity/entities access?

1. one entity reference (actor)

2. one action

3. multiple resources

If entitled, checks obligation policy: fulfillable obligations must satisfy all triggered.

Note: this is a more performant bulk request for multiple resource decisions, up to 1000 per request

+

Can the identified entity/entities access?

1. one entity reference (actor)

2. one action

3. multiple resources

If entitled, checks obligation policy: fulfillable obligations must satisfy all triggered.

Note: this is a more performant bulk request for multiple resource decisions

@@ -5620,7 +5620,7 @@

Resource

- + diff --git a/docs/openapi/authorization/v2/authorization.openapi.yaml b/docs/openapi/authorization/v2/authorization.openapi.yaml index 0f4f92d55f..6cd309e703 100644 --- a/docs/openapi/authorization/v2/authorization.openapi.yaml +++ b/docs/openapi/authorization/v2/authorization.openapi.yaml @@ -241,10 +241,10 @@ components: entityChain: title: entity_chain description: |+ - chain of one or more entities and at most 10 - entities must be provided and between 1 and 10 in count: + chain of one or more entities + entities must be provided and contain at least 1 entity: ``` - has(this.entities) && this.entities.size() > 0 && this.entities.size() <= 10 + has(this.entities) && this.entities.size() > 0 ``` $ref: '#/components/schemas/entity.EntityChain' @@ -305,7 +305,6 @@ components: items: $ref: '#/components/schemas/authorization.v2.GetDecisionMultiResourceRequest' title: decision_requests - maxItems: 200 minItems: 1 title: GetDecisionBulkRequest additionalProperties: false @@ -340,25 +339,24 @@ components: items: $ref: '#/components/schemas/authorization.v2.Resource' title: resources - maxItems: 1000 minItems: 1 fulfillableObligationFqns: type: array items: type: string description: |+ - if provided, fulfillable_obligation_fqns must be between 1 and 50 in count with all valid FQNs: + if provided, fulfillable_obligation_fqns must contain only valid FQNs: ``` - this.size() == 0 || (this.size() <= 50 && this.all(item, item.isUri())) + this.size() == 0 || this.all(item, item.isUri()) ``` title: fulfillable_obligation_fqns description: |+ obligations (fully qualified values) the requester is capable of fulfilling i.e. https:///obl//value/ - if provided, fulfillable_obligation_fqns must be between 1 and 50 in count with all valid FQNs: + if provided, fulfillable_obligation_fqns must contain only valid FQNs: ``` - this.size() == 0 || (this.size() <= 50 && this.all(item, item.isUri())) + this.size() == 0 || this.all(item, item.isUri()) ``` title: GetDecisionMultiResourceRequest @@ -374,7 +372,7 @@ components: If entitled, checks obligation policy: fulfillable obligations must satisfy all triggered. - Note: this is a more performant bulk request for multiple resource decisions, up to 1000 per request + Note: this is a more performant bulk request for multiple resource decisions action.name must be provided: ``` has(this.action.name) @@ -414,18 +412,18 @@ components: items: type: string description: |+ - if provided, fulfillable_obligation_fqns must be between 1 and 50 in count with all valid FQNs: + if provided, fulfillable_obligation_fqns must contain only valid FQNs: ``` - this.size() == 0 || (this.size() <= 50 && this.all(item, item.isUri())) + this.size() == 0 || this.all(item, item.isUri()) ``` title: fulfillable_obligation_fqns description: |+ obligations (fully qualified values) the requester is capable of fulfilling i.e. https:///obl//value/ - if provided, fulfillable_obligation_fqns must be between 1 and 50 in count with all valid FQNs: + if provided, fulfillable_obligation_fqns must contain only valid FQNs: ``` - this.size() == 0 || (this.size() <= 50 && this.all(item, item.isUri())) + this.size() == 0 || this.all(item, item.isUri()) ``` title: GetDecisionRequest @@ -495,10 +493,10 @@ components: attributeValues: title: attribute_values description: |+ - a set of attribute value FQNs, such as those on a TDF, between 1 and 20 in count - if provided, resource.attribute_values must be between 1 and 20 in count with all valid FQNs: + a set of attribute value FQNs, such as those on a TDF, containing at least 1 value + if provided, resource.attribute_values must contain at least 1 valid FQN: ``` - this.fqns.size() > 0 && this.fqns.size() <= 20 && this.fqns.all(item, item.isUri()) + this.fqns.size() > 0 && this.fqns.all(item, item.isUri()) ``` $ref: '#/components/schemas/authorization.v2.Resource.AttributeValues' diff --git a/docs/openapi/entityresolution/v2/entity_resolution.openapi.yaml b/docs/openapi/entityresolution/v2/entity_resolution.openapi.yaml index b8e661c75a..b56324b9c4 100644 --- a/docs/openapi/entityresolution/v2/entity_resolution.openapi.yaml +++ b/docs/openapi/entityresolution/v2/entity_resolution.openapi.yaml @@ -98,10 +98,10 @@ components: attributeValues: title: attribute_values description: |+ - a set of attribute value FQNs, such as those on a TDF, between 1 and 20 in count - if provided, resource.attribute_values must be between 1 and 20 in count with all valid FQNs: + a set of attribute value FQNs, such as those on a TDF, containing at least 1 value + if provided, resource.attribute_values must contain at least 1 valid FQN: ``` - this.fqns.size() > 0 && this.fqns.size() <= 20 && this.fqns.all(item, item.isUri()) + this.fqns.size() > 0 && this.fqns.all(item, item.isUri()) ``` $ref: '#/components/schemas/authorization.v2.Resource.AttributeValues' diff --git a/protocol/go/authorization/v2/authorization.pb.go b/protocol/go/authorization/v2/authorization.pb.go index 5adec7b3ca..9c1aac68c4 100644 --- a/protocol/go/authorization/v2/authorization.pb.go +++ b/protocol/go/authorization/v2/authorization.pb.go @@ -162,7 +162,7 @@ type isEntityIdentifier_Identifier interface { } type EntityIdentifier_EntityChain struct { - // chain of one or more entities and at most 10 + // chain of one or more entities EntityChain *entity.EntityChain `protobuf:"bytes,1,opt,name=entity_chain,json=entityChain,proto3,oneof"` } @@ -327,7 +327,7 @@ type isResource_Resource interface { } type Resource_AttributeValues_ struct { - // a set of attribute value FQNs, such as those on a TDF, between 1 and 20 in count + // a set of attribute value FQNs, such as those on a TDF, containing at least 1 value AttributeValues *Resource_AttributeValues `protobuf:"bytes,2,opt,name=attribute_values,json=attributeValues,proto3,oneof"` } @@ -543,7 +543,7 @@ func (x *GetDecisionResponse) GetDecision() *ResourceDecision { // // If entitled, checks obligation policy: fulfillable obligations must satisfy all triggered. // -// Note: this is a more performant bulk request for multiple resource decisions, up to 1000 per request +// Note: this is a more performant bulk request for multiple resource decisions type GetDecisionMultiResourceRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -990,291 +990,280 @@ var file_authorization_v2_authorization_proto_rawDesc = []byte{ 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x22, 0x87, 0x05, 0x0a, 0x10, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0xe1, 0x01, 0x0a, 0x0c, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x22, 0xe9, 0x04, 0x0a, 0x10, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0xc3, 0x01, 0x0a, 0x0c, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x68, 0x61, 0x69, - 0x6e, 0x42, 0xa6, 0x01, 0xba, 0x48, 0xa2, 0x01, 0xba, 0x01, 0x9e, 0x01, 0x0a, 0x15, 0x65, 0x6e, + 0x6e, 0x42, 0x88, 0x01, 0xba, 0x48, 0x84, 0x01, 0xba, 0x01, 0x80, 0x01, 0x0a, 0x15, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x37, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x61, - 0x6e, 0x64, 0x20, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x20, 0x31, 0x20, 0x61, 0x6e, 0x64, - 0x20, 0x31, 0x30, 0x20, 0x69, 0x6e, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x4c, 0x68, 0x61, + 0x6e, 0x64, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x61, 0x74, 0x20, 0x6c, 0x65, + 0x61, 0x73, 0x74, 0x20, 0x31, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x1a, 0x2e, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, - 0x73, 0x2e, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x29, 0x20, 0x3e, 0x20, 0x30, 0x20, 0x26, 0x26, 0x20, - 0x74, 0x68, 0x69, 0x73, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x2e, 0x73, 0x69, - 0x7a, 0x65, 0x28, 0x29, 0x20, 0x3c, 0x3d, 0x20, 0x31, 0x30, 0x48, 0x00, 0x52, 0x0b, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x4f, 0x0a, 0x1d, 0x72, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x71, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x0a, 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0x88, 0x01, 0x01, 0x48, 0x00, 0x52, 0x1a, - 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x71, 0x6e, 0x12, 0x7b, 0x0a, 0x05, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x54, 0xba, 0x48, 0x51, 0xba, 0x01, 0x4e, - 0x0a, 0x0e, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, - 0x12, 0x16, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x1a, 0x24, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, - 0x69, 0x73, 0x2e, 0x6a, 0x77, 0x74, 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, - 0x6a, 0x77, 0x74, 0x2e, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x29, 0x20, 0x3e, 0x20, 0x30, 0x48, 0x00, - 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0xab, 0x01, 0x0a, 0x12, 0x77, 0x69, 0x74, 0x68, - 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x42, 0x5f, 0xba, 0x48, 0x5c, 0xba, 0x01, 0x59, 0x0a, 0x1f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x6d, 0x75, 0x73, - 0x74, 0x5f, 0x62, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x12, 0x28, 0x77, 0x69, 0x74, 0x68, 0x5f, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x6d, 0x75, - 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x74, 0x72, 0x75, 0x65, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, - 0x73, 0x65, 0x74, 0x1a, 0x0c, 0x74, 0x68, 0x69, 0x73, 0x20, 0x3d, 0x3d, 0x20, 0x74, 0x72, 0x75, - 0x65, 0x48, 0x00, 0x52, 0x10, 0x77, 0x69, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x13, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, - 0x69, 0x65, 0x72, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, 0x22, 0x81, 0x03, 0x0a, 0x12, 0x45, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, - 0x61, 0x6c, 0x49, 0x64, 0x12, 0x8b, 0x01, 0x0a, 0x1f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x71, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x45, - 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, - 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x65, 0x72, 0x41, - 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x71, 0x6e, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x1b, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x65, + 0x73, 0x2e, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x29, 0x20, 0x3e, 0x20, 0x30, 0x48, 0x00, 0x52, 0x0b, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x4f, 0x0a, 0x1d, 0x72, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x71, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0x88, 0x01, 0x01, 0x48, 0x00, + 0x52, 0x1a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x71, 0x6e, 0x12, 0x7b, 0x0a, 0x05, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x54, 0xba, 0x48, 0x51, 0xba, + 0x01, 0x4e, 0x0a, 0x0e, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, + 0x65, 0x64, 0x12, 0x16, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, + 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x1a, 0x24, 0x68, 0x61, 0x73, 0x28, + 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6a, 0x77, 0x74, 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x2e, 0x6a, 0x77, 0x74, 0x2e, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x29, 0x20, 0x3e, 0x20, 0x30, + 0x48, 0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0xab, 0x01, 0x0a, 0x12, 0x77, 0x69, + 0x74, 0x68, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x42, 0x5f, 0xba, 0x48, 0x5c, 0xba, 0x01, 0x59, 0x0a, 0x1f, 0x77, 0x69, 0x74, 0x68, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x6d, + 0x75, 0x73, 0x74, 0x5f, 0x62, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x12, 0x28, 0x77, 0x69, 0x74, + 0x68, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, + 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x74, 0x72, 0x75, 0x65, 0x20, 0x77, 0x68, 0x65, + 0x6e, 0x20, 0x73, 0x65, 0x74, 0x1a, 0x0c, 0x74, 0x68, 0x69, 0x73, 0x20, 0x3d, 0x3d, 0x20, 0x74, + 0x72, 0x75, 0x65, 0x48, 0x00, 0x52, 0x10, 0x77, 0x69, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x13, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, 0x22, 0x81, 0x03, 0x0a, + 0x12, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x70, 0x68, 0x65, 0x6d, + 0x65, 0x72, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x8b, 0x01, 0x0a, 0x1f, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x71, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x45, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x65, 0x72, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x46, - 0x71, 0x6e, 0x1a, 0x37, 0x0a, 0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4c, 0x69, 0x73, - 0x74, 0x12, 0x28, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x80, 0x01, 0x0a, 0x20, - 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x65, 0x72, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x71, 0x6e, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x46, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x30, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, - 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xe4, - 0x03, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x65, - 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x49, 0x64, 0x12, 0xb0, - 0x02, 0x0a, 0x10, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x73, 0x42, 0xd6, 0x01, 0xba, 0x48, 0xd2, 0x01, 0xba, 0x01, 0xce, 0x01, - 0x0a, 0x19, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x5c, 0x69, 0x66, 0x20, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x2c, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x73, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x62, 0x65, 0x74, 0x77, - 0x65, 0x65, 0x6e, 0x20, 0x31, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x32, 0x30, 0x20, 0x69, 0x6e, 0x20, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x76, - 0x61, 0x6c, 0x69, 0x64, 0x20, 0x46, 0x51, 0x4e, 0x73, 0x1a, 0x53, 0x74, 0x68, 0x69, 0x73, 0x2e, - 0x66, 0x71, 0x6e, 0x73, 0x2e, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x29, 0x20, 0x3e, 0x20, 0x30, 0x20, - 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x66, 0x71, 0x6e, 0x73, 0x2e, 0x73, 0x69, 0x7a, - 0x65, 0x28, 0x29, 0x20, 0x3c, 0x3d, 0x20, 0x32, 0x30, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, - 0x73, 0x2e, 0x66, 0x71, 0x6e, 0x73, 0x2e, 0x61, 0x6c, 0x6c, 0x28, 0x69, 0x74, 0x65, 0x6d, 0x2c, - 0x20, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x69, 0x73, 0x55, 0x72, 0x69, 0x28, 0x29, 0x29, 0x48, 0x00, - 0x52, 0x0f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x73, 0x12, 0x4f, 0x0a, 0x1d, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x5f, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, - 0x71, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, - 0x01, 0x88, 0x01, 0x01, 0x48, 0x00, 0x52, 0x1a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x46, - 0x71, 0x6e, 0x1a, 0x25, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x6e, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x6e, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xb1, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x15, 0x65, 0x70, - 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x65, 0x70, 0x68, 0x65, 0x6d, - 0x65, 0x72, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x36, - 0x0a, 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x1a, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, 0x65, - 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x14, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, - 0x65, 0x64, 0x5f, 0x6f, 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4f, 0x62, - 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xd6, 0x04, 0x0a, 0x12, 0x47, 0x65, - 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x57, 0x0a, 0x11, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x45, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x42, - 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x10, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x06, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, - 0x01, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x08, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x75, + 0x71, 0x6e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x1b, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x50, 0x65, 0x72, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x46, 0x71, 0x6e, 0x1a, 0x37, 0x0a, 0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x80, 0x01, + 0x0a, 0x20, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x65, 0x72, 0x41, 0x74, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x71, 0x6e, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x46, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0xb6, 0x03, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x49, 0x64, + 0x12, 0x82, 0x02, 0x0a, 0x10, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, - 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x8f, 0x02, 0x0a, 0x1b, 0x66, 0x75, - 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6f, 0x62, 0x6c, 0x69, 0x67, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x42, - 0xce, 0x01, 0xba, 0x48, 0xca, 0x01, 0xba, 0x01, 0xc6, 0x01, 0x0a, 0x1b, 0x6f, 0x62, 0x6c, 0x69, - 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x71, 0x6e, - 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x5e, 0x69, 0x66, 0x20, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x64, 0x2c, 0x20, 0x66, 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x61, 0x62, 0x6c, - 0x65, 0x5f, 0x6f, 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x71, 0x6e, - 0x73, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, - 0x6e, 0x20, 0x31, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x35, 0x30, 0x20, 0x69, 0x6e, 0x20, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x76, 0x61, 0x6c, - 0x69, 0x64, 0x20, 0x46, 0x51, 0x4e, 0x73, 0x1a, 0x47, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x73, 0x69, - 0x7a, 0x65, 0x28, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x30, 0x20, 0x7c, 0x7c, 0x20, 0x28, 0x74, 0x68, - 0x69, 0x73, 0x2e, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x29, 0x20, 0x3c, 0x3d, 0x20, 0x35, 0x30, 0x20, - 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x61, 0x6c, 0x6c, 0x28, 0x69, 0x74, 0x65, 0x6d, - 0x2c, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x69, 0x73, 0x55, 0x72, 0x69, 0x28, 0x29, 0x29, 0x29, - 0x52, 0x19, 0x66, 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x62, 0x6c, - 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x71, 0x6e, 0x73, 0x3a, 0x65, 0xba, 0x48, 0x62, - 0x1a, 0x60, 0x0a, 0x29, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x1c, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, - 0x62, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x1a, 0x15, 0x68, 0x61, 0x73, - 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x6e, 0x61, 0x6d, - 0x65, 0x29, 0x22, 0x55, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x08, 0x64, 0x65, 0x63, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xf0, 0x04, 0x0a, 0x1f, 0x47, 0x65, - 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x57, 0x0a, - 0x11, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, - 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x42, 0x06, 0xba, 0x48, - 0x03, 0xc8, 0x01, 0x01, 0x52, 0x10, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, - 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x06, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x45, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x42, 0x0b, 0xba, 0x48, 0x08, 0x92, 0x01, 0x05, 0x08, 0x01, 0x10, - 0xe8, 0x07, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x8f, 0x02, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x42, 0xa8, 0x01, 0xba, 0x48, 0xa4, 0x01, 0xba, 0x01, + 0xa0, 0x01, 0x0a, 0x19, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x48, 0x69, + 0x66, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x2c, 0x20, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x73, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x20, 0x61, 0x74, 0x20, 0x6c, 0x65, 0x61, 0x73, 0x74, 0x20, 0x31, 0x20, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x20, 0x46, 0x51, 0x4e, 0x1a, 0x39, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x66, 0x71, + 0x6e, 0x73, 0x2e, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x29, 0x20, 0x3e, 0x20, 0x30, 0x20, 0x26, 0x26, + 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x66, 0x71, 0x6e, 0x73, 0x2e, 0x61, 0x6c, 0x6c, 0x28, 0x69, + 0x74, 0x65, 0x6d, 0x2c, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x69, 0x73, 0x55, 0x72, 0x69, 0x28, + 0x29, 0x29, 0x48, 0x00, 0x52, 0x0f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x4f, 0x0a, 0x1d, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x5f, 0x66, 0x71, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, + 0x07, 0x72, 0x05, 0x10, 0x01, 0x88, 0x01, 0x01, 0x48, 0x00, 0x52, 0x1a, 0x72, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x46, 0x71, 0x6e, 0x1a, 0x25, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x6e, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x6e, 0x73, 0x42, 0x0a, 0x0a, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xb1, 0x01, 0x0a, 0x10, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x32, + 0x0a, 0x15, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x65, + 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x49, 0x64, 0x12, 0x36, 0x0a, 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x52, 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x14, 0x72, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x6f, 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, + 0x65, 0x64, 0x4f, 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xa6, 0x04, + 0x0a, 0x12, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x57, 0x0a, 0x11, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x22, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x76, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x10, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x2e, 0x0a, + 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x06, 0xba, + 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x76, 0x32, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x42, 0x06, 0xba, 0x48, 0x03, + 0xc8, 0x01, 0x01, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xdf, 0x01, 0x0a, 0x1b, 0x66, 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6f, 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x09, 0x42, 0xce, 0x01, 0xba, 0x48, 0xca, 0x01, 0xba, 0x01, 0xc6, 0x01, 0x0a, 0x1b, + 0x03, 0x28, 0x09, 0x42, 0x9e, 0x01, 0xba, 0x48, 0x9a, 0x01, 0xba, 0x01, 0x96, 0x01, 0x0a, 0x1b, 0x6f, 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x5e, 0x69, 0x66, 0x20, + 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x45, 0x69, 0x66, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x2c, 0x20, 0x66, 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6f, 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x62, 0x65, - 0x74, 0x77, 0x65, 0x65, 0x6e, 0x20, 0x31, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x35, 0x30, 0x20, 0x69, - 0x6e, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x6c, 0x6c, - 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x46, 0x51, 0x4e, 0x73, 0x1a, 0x47, 0x74, 0x68, 0x69, - 0x73, 0x2e, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x30, 0x20, 0x7c, 0x7c, - 0x20, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x29, 0x20, 0x3c, 0x3d, - 0x20, 0x35, 0x30, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x61, 0x6c, 0x6c, 0x28, - 0x69, 0x74, 0x65, 0x6d, 0x2c, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x69, 0x73, 0x55, 0x72, 0x69, - 0x28, 0x29, 0x29, 0x29, 0x52, 0x19, 0x66, 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x61, 0x62, 0x6c, + 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x46, 0x51, + 0x4e, 0x73, 0x1a, 0x30, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x29, 0x20, + 0x3d, 0x3d, 0x20, 0x30, 0x20, 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x61, 0x6c, 0x6c, + 0x28, 0x69, 0x74, 0x65, 0x6d, 0x2c, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x69, 0x73, 0x55, 0x72, + 0x69, 0x28, 0x29, 0x29, 0x52, 0x19, 0x66, 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x71, 0x6e, 0x73, 0x3a, - 0x6b, 0xba, 0x48, 0x68, 0x1a, 0x66, 0x0a, 0x2f, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x65, 0x63, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x72, - 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x1c, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x64, 0x1a, 0x15, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x29, 0x22, 0xb6, 0x01, 0x0a, - 0x20, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x75, 0x6c, 0x74, - 0x69, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3f, 0x0a, 0x0d, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x74, - 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0c, 0x61, 0x6c, 0x6c, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x74, - 0x65, 0x64, 0x12, 0x51, 0x0a, 0x12, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x64, - 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, - 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, - 0x32, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x52, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, 0x65, 0x63, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x85, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x6b, 0x0a, 0x11, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x47, - 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x0b, - 0xba, 0x48, 0x08, 0x92, 0x01, 0x05, 0x08, 0x01, 0x10, 0xc8, 0x01, 0x52, 0x10, 0x64, 0x65, 0x63, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x22, 0x7c, 0x0a, - 0x17, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x75, 0x6c, 0x6b, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x12, 0x64, 0x65, 0x63, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x11, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x22, 0xd9, 0x01, 0x0a, 0x16, - 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x57, 0x0a, 0x11, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x66, 0x69, 0x65, 0x72, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x10, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, - 0x45, 0x0a, 0x1c, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x68, 0x65, - 0x6e, 0x73, 0x69, 0x76, 0x65, 0x5f, 0x68, 0x69, 0x65, 0x72, 0x61, 0x72, 0x63, 0x68, 0x79, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x1a, 0x77, 0x69, 0x74, 0x68, 0x43, 0x6f, 0x6d, - 0x70, 0x72, 0x65, 0x68, 0x65, 0x6e, 0x73, 0x69, 0x76, 0x65, 0x48, 0x69, 0x65, 0x72, 0x61, 0x72, - 0x63, 0x68, 0x79, 0x88, 0x01, 0x01, 0x42, 0x1f, 0x0a, 0x1d, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, - 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x68, 0x65, 0x6e, 0x73, 0x69, 0x76, 0x65, 0x5f, 0x68, 0x69, - 0x65, 0x72, 0x61, 0x72, 0x63, 0x68, 0x79, 0x22, 0x63, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x45, 0x6e, - 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0c, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x0c, - 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2a, 0x4c, 0x0a, 0x08, - 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x45, 0x43, 0x49, - 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x44, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x44, - 0x45, 0x4e, 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, - 0x4e, 0x5f, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x54, 0x10, 0x02, 0x32, 0xce, 0x03, 0x0a, 0x14, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x5c, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x24, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x65, 0xba, 0x48, 0x62, 0x1a, 0x60, 0x0a, 0x29, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x65, 0x63, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, + 0x64, 0x12, 0x1c, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6d, + 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x1a, + 0x15, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x29, 0x22, 0x55, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, + 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x22, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x76, 0x32, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, 0x65, 0x63, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xbd, 0x04, + 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x75, 0x6c, + 0x74, 0x69, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x57, 0x0a, 0x11, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x10, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x06, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, + 0x01, 0x01, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x42, 0x0a, 0x09, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x42, 0x08, 0xba, 0x48, 0x05, 0x92, 0x01, + 0x02, 0x08, 0x01, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0xdf, + 0x01, 0x0a, 0x1b, 0x66, 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6f, + 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x09, 0x42, 0x9e, 0x01, 0xba, 0x48, 0x9a, 0x01, 0xba, 0x01, 0x96, 0x01, 0x0a, + 0x1b, 0x6f, 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x45, 0x69, 0x66, + 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x2c, 0x20, 0x66, 0x75, 0x6c, 0x66, 0x69, + 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6f, 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x46, + 0x51, 0x4e, 0x73, 0x1a, 0x30, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x29, + 0x20, 0x3d, 0x3d, 0x20, 0x30, 0x20, 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x61, 0x6c, + 0x6c, 0x28, 0x69, 0x74, 0x65, 0x6d, 0x2c, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x69, 0x73, 0x55, + 0x72, 0x69, 0x28, 0x29, 0x29, 0x52, 0x19, 0x66, 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x61, 0x62, + 0x6c, 0x65, 0x4f, 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x71, 0x6e, 0x73, + 0x3a, 0x6b, 0xba, 0x48, 0x68, 0x1a, 0x66, 0x0a, 0x2f, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x65, 0x63, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x1c, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x1a, 0x15, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, + 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x29, 0x22, 0xb6, 0x01, + 0x0a, 0x20, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x75, 0x6c, + 0x74, 0x69, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0d, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0c, 0x61, 0x6c, 0x6c, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x64, 0x12, 0x51, 0x0a, 0x12, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, + 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x22, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x76, 0x32, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, 0x65, 0x63, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, 0x65, 0x63, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x44, 0x65, + 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x68, 0x0a, 0x11, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, + 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x75, 0x6c, 0x74, 0x69, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, + 0x08, 0xba, 0x48, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x10, 0x64, 0x65, 0x63, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x22, 0x7c, 0x0a, 0x17, 0x47, + 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x12, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x6e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x11, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x22, 0xd9, 0x01, 0x0a, 0x16, 0x47, 0x65, + 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x57, 0x0a, 0x11, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x22, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x76, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x10, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x45, 0x0a, + 0x1c, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x68, 0x65, 0x6e, 0x73, + 0x69, 0x76, 0x65, 0x5f, 0x68, 0x69, 0x65, 0x72, 0x61, 0x72, 0x63, 0x68, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x1a, 0x77, 0x69, 0x74, 0x68, 0x43, 0x6f, 0x6d, 0x70, 0x72, + 0x65, 0x68, 0x65, 0x6e, 0x73, 0x69, 0x76, 0x65, 0x48, 0x69, 0x65, 0x72, 0x61, 0x72, 0x63, 0x68, + 0x79, 0x88, 0x01, 0x01, 0x42, 0x1f, 0x0a, 0x1d, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x63, 0x6f, + 0x6d, 0x70, 0x72, 0x65, 0x68, 0x65, 0x6e, 0x73, 0x69, 0x76, 0x65, 0x5f, 0x68, 0x69, 0x65, 0x72, + 0x61, 0x72, 0x63, 0x68, 0x79, 0x22, 0x63, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x48, 0x0a, 0x0c, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x0c, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2a, 0x4c, 0x0a, 0x08, 0x44, 0x65, + 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x45, 0x43, 0x49, 0x53, 0x49, + 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x11, 0x0a, 0x0d, 0x44, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x4e, + 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, + 0x50, 0x45, 0x52, 0x4d, 0x49, 0x54, 0x10, 0x02, 0x32, 0xce, 0x03, 0x0a, 0x14, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x5c, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x24, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x83, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, + 0x75, 0x6c, 0x74, 0x69, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x31, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, + 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x75, 0x6c, 0x74, 0x69, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x32, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x75, + 0x6c, 0x74, 0x69, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x68, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x75, 0x6c, 0x6b, 0x12, 0x28, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x44, - 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x83, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x31, - 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, - 0x32, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x75, 0x6c, - 0x74, 0x69, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x32, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x68, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x44, 0x65, - 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x75, 0x6c, 0x6b, 0x12, 0x28, 0x2e, 0x61, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, - 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x68, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x12, 0x28, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, - 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, - 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, - 0x32, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xd5, 0x01, 0x0a, 0x14, - 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x2e, 0x76, 0x32, 0x42, 0x12, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x48, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x64, 0x66, 0x2f, 0x70, - 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, - 0x2f, 0x67, 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x2f, 0x76, 0x32, 0x3b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x76, 0x32, 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, 0xaa, 0x02, 0x10, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x56, 0x32, 0xca, 0x02, 0x10, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x56, 0x32, - 0xe2, 0x02, 0x1c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5c, 0x56, 0x32, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, - 0x02, 0x11, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, - 0x3a, 0x56, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x68, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x12, 0x28, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, + 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xd5, 0x01, 0x0a, 0x14, 0x63, 0x6f, + 0x6d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x76, 0x32, 0x42, 0x12, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x48, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x64, 0x66, 0x2f, 0x70, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x67, + 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, + 0x76, 0x32, 0x3b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x76, 0x32, 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, 0xaa, 0x02, 0x10, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x56, 0x32, 0xca, 0x02, 0x10, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x56, 0x32, 0xe2, 0x02, + 0x1c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x56, + 0x32, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x11, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x3a, 0x56, + 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/service/authorization/v2/authorization.go b/service/authorization/v2/authorization.go index b90b56db3d..c2bdc21feb 100644 --- a/service/authorization/v2/authorization.go +++ b/service/authorization/v2/authorization.go @@ -166,6 +166,10 @@ func (as *Service) GetDecision(ctx context.Context, req *connect.Request[authzV2 ctx, span := as.Start(ctx, "GetDecision") defer span.End() + if err := as.validateGetDecisionRequest(req.Msg); err != nil { + return nil, err + } + pdp, err := access.NewJustInTimePDP(ctx, as.logger, as.sdk, as.cache, as.config.AllowDirectEntitlements, as.config.EnforceNamespacedEntitlements) if err != nil { return nil, statusifyError(ctx, as.logger, errors.Join(ErrFailedToInitPDP, err)) @@ -212,6 +216,10 @@ func (as *Service) GetDecisionMultiResource(ctx context.Context, req *connect.Re ctx, span := as.Start(ctx, "GetDecisionMultiResource") defer span.End() + if err := as.validateGetDecisionMultiResourceRequest(req.Msg, ""); err != nil { + return nil, err + } + pdp, err := access.NewJustInTimePDP(ctx, as.logger, as.sdk, as.cache, as.config.AllowDirectEntitlements, as.config.EnforceNamespacedEntitlements) if err != nil { return nil, statusifyError(ctx, as.logger, errors.Join(ErrFailedToInitPDP, err)) @@ -261,6 +269,10 @@ func (as *Service) GetDecisionBulk(ctx context.Context, req *connect.Request[aut ctx, span := as.Start(ctx, "GetDecisionBulk") defer span.End() + if err := as.validateGetDecisionBulkRequest(req.Msg); err != nil { + return nil, err + } + pdp, err := access.NewJustInTimePDP(ctx, as.logger, as.sdk, as.cache, as.config.AllowDirectEntitlements, as.config.EnforceNamespacedEntitlements) if err != nil { return nil, statusifyError(ctx, as.logger, errors.Join(ErrFailedToInitPDP, err)) diff --git a/service/authorization/v2/authorization.proto b/service/authorization/v2/authorization.proto index 83f9bd3ff9..7c325390a7 100644 --- a/service/authorization/v2/authorization.proto +++ b/service/authorization/v2/authorization.proto @@ -20,11 +20,11 @@ message EntityIdentifier { oneof identifier { option (buf.validate.oneof).required = true; - // chain of one or more entities and at most 10 + // chain of one or more entities entity.EntityChain entity_chain = 1 [(buf.validate.field).cel = { id: "entity_chain_required" - message: "entities must be provided and between 1 and 10 in count" - expression: "has(this.entities) && this.entities.size() > 0 && this.entities.size() <= 10" + message: "entities must be provided and contain at least 1 entity" + expression: "has(this.entities) && this.entities.size() > 0" }]; // fully qualified name of the registered resource value stored in platform policy, where in @@ -71,11 +71,11 @@ message Resource { } oneof resource { - // a set of attribute value FQNs, such as those on a TDF, between 1 and 20 in count + // a set of attribute value FQNs, such as those on a TDF, containing at least 1 value AttributeValues attribute_values = 2 [(buf.validate.field).cel = { id: "attribute_values_required" - message: "if provided, resource.attribute_values must be between 1 and 20 in count with all valid FQNs" - expression: "this.fqns.size() > 0 && this.fqns.size() <= 20 && this.fqns.all(item, item.isUri())" + message: "if provided, resource.attribute_values must contain at least 1 valid FQN" + expression: "this.fqns.size() > 0 && this.fqns.all(item, item.isUri())" }]; // fully qualified name of the registered resource value stored in platform policy string registered_resource_value_fqn = 3 [(buf.validate.field).string = { @@ -112,8 +112,8 @@ message GetDecisionRequest { // i.e. https:///obl//value/ repeated string fulfillable_obligation_fqns = 4 [(buf.validate.field).cel = { id: "obligation_value_fqns_valid" - message: "if provided, fulfillable_obligation_fqns must be between 1 and 50 in count with all valid FQNs" - expression: "this.size() == 0 || (this.size() <= 50 && this.all(item, item.isUri()))" + message: "if provided, fulfillable_obligation_fqns must contain only valid FQNs" + expression: "this.size() == 0 || this.all(item, item.isUri())" }]; option (buf.validate.message).cel = { @@ -134,7 +134,7 @@ message GetDecisionResponse { // // If entitled, checks obligation policy: fulfillable obligations must satisfy all triggered. // -// Note: this is a more performant bulk request for multiple resource decisions, up to 1000 per request +// Note: this is a more performant bulk request for multiple resource decisions message GetDecisionMultiResourceRequest { // an entity must be identified for authorization decisioning EntityIdentifier entity_identifier = 1 [(buf.validate.field).required = true]; @@ -142,15 +142,14 @@ message GetDecisionMultiResourceRequest { policy.Action action = 2 [(buf.validate.field).required = true]; repeated Resource resources = 3 [(buf.validate.field).repeated = { min_items: 1 - max_items: 1000 }]; // obligations (fully qualified values) the requester is capable of fulfilling // i.e. https:///obl//value/ repeated string fulfillable_obligation_fqns = 4 [(buf.validate.field).cel = { id: "obligation_value_fqns_valid" - message: "if provided, fulfillable_obligation_fqns must be between 1 and 50 in count with all valid FQNs" - expression: "this.size() == 0 || (this.size() <= 50 && this.all(item, item.isUri()))" + message: "if provided, fulfillable_obligation_fqns must contain only valid FQNs" + expression: "this.size() == 0 || this.all(item, item.isUri())" }]; option (buf.validate.message).cel = { @@ -173,7 +172,6 @@ message GetDecisionMultiResourceResponse { message GetDecisionBulkRequest { repeated GetDecisionMultiResourceRequest decision_requests = 1 [(buf.validate.field).repeated = { min_items: 1 - max_items: 200 }]; } message GetDecisionBulkResponse { diff --git a/service/authorization/v2/authorization_test.go b/service/authorization/v2/authorization_test.go index 0d0e80259f..7ca65eedad 100644 --- a/service/authorization/v2/authorization_test.go +++ b/service/authorization/v2/authorization_test.go @@ -376,42 +376,6 @@ var ( }, expectedValidationError: "registered_resource_value_fqn", }, - { - name: "too many obligations", - request: &authzV2.GetDecisionMultiResourceRequest{ - EntityIdentifier: &authzV2.EntityIdentifier{ - Identifier: &authzV2.EntityIdentifier_EntityChain{ - EntityChain: &entity.EntityChain{ - EphemeralId: "1234", - Entities: []*entity.Entity{ - { - EphemeralId: "chained-1", - EntityType: &entity.Entity_EmailAddress{EmailAddress: "test@test.com"}, - Category: entity.Entity_CATEGORY_SUBJECT, - }, - }, - }, - }, - }, - Action: sampleActionCreate, - Resources: []*authzV2.Resource{ - { - Resource: &authzV2.Resource_AttributeValues_{ - AttributeValues: &authzV2.Resource_AttributeValues{ - Fqns: []string{sampleResourceFQN}, - }, - }, - }, - { - Resource: &authzV2.Resource_RegisteredResourceValueFqn{ - RegisteredResourceValueFqn: sampleRegisteredResourceFQN, - }, - }, - }, - FulfillableObligationFqns: getTooManyObligations(), - }, - expectedValidationError: "obligation_value_fqns_valid", - }, { name: "invalid obligation", request: &authzV2.GetDecisionMultiResourceRequest{ @@ -443,7 +407,7 @@ func getValidator() protovalidate.Validator { return v } -func Test_EntityIdentifier_ManyChainedEntities(t *testing.T) { +func Test_EntityIdentifier_ProtoValidationAllowsManyChainedEntities(t *testing.T) { v := getValidator() // many entities in chain entityIdentifier := &authzV2.EntityIdentifier{ @@ -471,10 +435,10 @@ func Test_EntityIdentifier_ManyChainedEntities(t *testing.T) { Category: entity.Entity_CATEGORY_SUBJECT, }) err = v.Validate(entityIdentifier) - require.Error(t, err, "validation should fail for request with 11 entities in chain") + require.NoError(t, err, "validation should allow more than 10 entities in chain so the service can enforce configured limits") } -func Test_Resource_ManyAttributeValues(t *testing.T) { +func Test_Resource_ProtoValidationAllowsManyAttributeValues(t *testing.T) { v := getValidator() resource := &authzV2.Resource{ Resource: &authzV2.Resource_AttributeValues_{ @@ -492,7 +456,7 @@ func Test_Resource_ManyAttributeValues(t *testing.T) { // add one more resource.GetAttributeValues().Fqns = append(resource.GetAttributeValues().Fqns, "https://example.com/attr/any_of_attr_name/value/val20") err = v.Validate(resource) - require.Error(t, err, "validation should fail for request with 21 attribute values") + require.NoError(t, err, "validation should allow more than 20 attribute values so the service can enforce configured limits") } func Test_GetDecisionRequest_Succeeds(t *testing.T) { @@ -501,6 +465,7 @@ func Test_GetDecisionRequest_Succeeds(t *testing.T) { for i := range 50 { fiftyObligations[i] = sampleObligationValueFQN } + fiftyOneObligations := append(append([]string{}, fiftyObligations...), sampleObligationValueFQN) cases := []struct { name string @@ -693,6 +658,23 @@ func Test_GetDecisionRequest_Succeeds(t *testing.T) { FulfillableObligationFqns: fiftyObligations, }, }, + { + name: "entity: registered resource, action: create, resource: registered, obligations - 51", + request: &authzV2.GetDecisionRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + Action: sampleActionCreate, + Resource: &authzV2.Resource{ + Resource: &authzV2.Resource_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + FulfillableObligationFqns: fiftyOneObligations, + }, + }, } for _, tc := range cases { @@ -945,24 +927,6 @@ func Test_GetDecisionRequest_Fails(t *testing.T) { }, expectedValidationError: "entities", }, - { - name: "too many obligations", - request: &authzV2.GetDecisionRequest{ - EntityIdentifier: &authzV2.EntityIdentifier{ - Identifier: &authzV2.EntityIdentifier_RegisteredResourceValueFqn{ - RegisteredResourceValueFqn: sampleRegisteredResourceFQN, - }, - }, - Action: sampleActionCreate, - Resource: &authzV2.Resource{ - Resource: &authzV2.Resource_RegisteredResourceValueFqn{ - RegisteredResourceValueFqn: sampleRegisteredResourceFQN, - }, - }, - FulfillableObligationFqns: getTooManyObligations(), - }, - expectedValidationError: "obligation_value_fqns_valid", - }, { name: "invalid obligation format", request: &authzV2.GetDecisionRequest{ @@ -1009,7 +973,7 @@ func Test_GetDecisionMultiResourceRequest_Succeeds(t *testing.T) { } } -func Test_GetDecisionMultiResourceRequest_ResourceLimit(t *testing.T) { +func Test_GetDecisionMultiResourceRequest_ProtoValidationAllowsManyResources(t *testing.T) { v := getValidator() upperBoundLimit := 1000 @@ -1046,7 +1010,7 @@ func Test_GetDecisionMultiResourceRequest_ResourceLimit(t *testing.T) { }, }) err = v.Validate(req) - require.Error(t, err, "validation should fail for request with 1001 resources") + require.NoError(t, err, "validation should allow more than 1000 resources so the service can enforce configured limits") } func Test_GetDecisionMultiResourceRequest_Fails(t *testing.T) { @@ -1145,13 +1109,15 @@ func Test_GetDecisionBulkRequest_Fails(t *testing.T) { } } -func Test_GetDecisionBulkRequest_Limits(t *testing.T) { +func Test_GetDecisionBulkRequest_RequiresAtLeastOneDecisionRequest(t *testing.T) { v := getValidator() - // requests must be between 1 and 200 req := &authzV2.GetDecisionBulkRequest{} err := v.Validate(req) require.Error(t, err, "validation should fail for bulk request without any multi resource decision requests") +} +func Test_GetDecisionBulkRequest_ProtoValidationAllowsManyDecisionRequests(t *testing.T) { + v := getValidator() dr := &authzV2.GetDecisionMultiResourceRequest{ EntityIdentifier: &authzV2.EntityIdentifier{ Identifier: &authzV2.EntityIdentifier_Token{ @@ -1172,16 +1138,17 @@ func Test_GetDecisionBulkRequest_Limits(t *testing.T) { }, }, } + req := &authzV2.GetDecisionBulkRequest{} req.DecisionRequests = make([]*authzV2.GetDecisionMultiResourceRequest, 201) for i := range req.GetDecisionRequests() { req.DecisionRequests[i] = dr } - err = v.Validate(req) - require.Error(t, err, "validation should fail for bulk request with more than 200 multi resource decision requests") + err := v.Validate(req) + require.NoError(t, err, "validation should allow more than 200 decision requests so the service can enforce configured limits") req.DecisionRequests = append(req.GetDecisionRequests(), dr) err = v.Validate(req) - require.Error(t, err, "validation should fail for bulk request with more than 200 multi resource decision requests") + require.NoError(t, err, "validation should allow more than 200 decision requests so the service can enforce configured limits") } func Test_GetEntitlementsRequest_Succeeds(t *testing.T) { @@ -1616,14 +1583,6 @@ func getRandomValidObligationValueFQNsList() []string { return randomList } -func getTooManyObligations() []string { - tooMany := make([]string, 51) - for i := range tooMany { - tooMany[i] = getRandomObligationValueFQN() - } - return tooMany -} - const charset = "abcdefghijklmnopqrstuvwxyz" func randString(length int) string { diff --git a/service/authorization/v2/config.go b/service/authorization/v2/config.go index 44e16a8c34..bfadec2829 100644 --- a/service/authorization/v2/config.go +++ b/service/authorization/v2/config.go @@ -13,8 +13,36 @@ type EntitlementPolicyCacheConfig struct { RefreshInterval string `mapstructure:"refresh_interval" json:"refresh_interval" default:"30s"` } +type RequestLimitsConfig struct { + ResourceAttributeValuesMax int `mapstructure:"resource_attribute_values_max" json:"resource_attribute_values_max" default:"20"` + EntityChainEntitiesMax int `mapstructure:"entity_chain_entities_max" json:"entity_chain_entities_max" default:"10"` + FulfillableObligationFqnsMax int `mapstructure:"fulfillable_obligation_fqns_max" json:"fulfillable_obligation_fqns_max" default:"50"` + MultiResourceRequestMax int `mapstructure:"multi_resource_request_max" json:"multi_resource_request_max" default:"1000"` + BulkDecisionRequestMax int `mapstructure:"bulk_decision_request_max" json:"bulk_decision_request_max" default:"200"` +} + +func (c RequestLimitsConfig) Validate() error { + if c.ResourceAttributeValuesMax < 1 { + return fmt.Errorf("authorization request limit resource_attribute_values_max [%d] must be greater than 0", c.ResourceAttributeValuesMax) + } + if c.EntityChainEntitiesMax < 1 { + return fmt.Errorf("authorization request limit entity_chain_entities_max [%d] must be greater than 0", c.EntityChainEntitiesMax) + } + if c.FulfillableObligationFqnsMax < 0 { + return fmt.Errorf("authorization request limit fulfillable_obligation_fqns_max [%d] must be greater than or equal to 0", c.FulfillableObligationFqnsMax) + } + if c.MultiResourceRequestMax < 1 { + return fmt.Errorf("authorization request limit multi_resource_request_max [%d] must be greater than 0", c.MultiResourceRequestMax) + } + if c.BulkDecisionRequestMax < 1 { + return fmt.Errorf("authorization request limit bulk_decision_request_max [%d] must be greater than 0", c.BulkDecisionRequestMax) + } + return nil +} + type Config struct { - Cache EntitlementPolicyCacheConfig `mapstructure:"entitlement_policy_cache" json:"entitlement_policy_cache"` + Cache EntitlementPolicyCacheConfig `mapstructure:"entitlement_policy_cache" json:"entitlement_policy_cache"` + RequestLimits RequestLimitsConfig `mapstructure:"request_limits" json:"request_limits"` // experimental features @@ -48,6 +76,9 @@ func (c *Config) Validate() error { ) } } + if err := c.RequestLimits.Validate(); err != nil { + return fmt.Errorf("invalid authorization request limits config: %w", err) + } return nil } @@ -59,6 +90,15 @@ func (c *Config) LogValue() slog.Value { slog.String("refresh_interval", c.Cache.RefreshInterval), ), ), + slog.Any("request_limits", + slog.GroupValue( + slog.Int("resource_attribute_values_max", c.RequestLimits.ResourceAttributeValuesMax), + slog.Int("entity_chain_entities_max", c.RequestLimits.EntityChainEntitiesMax), + slog.Int("fulfillable_obligation_fqns_max", c.RequestLimits.FulfillableObligationFqnsMax), + slog.Int("multi_resource_request_max", c.RequestLimits.MultiResourceRequestMax), + slog.Int("bulk_decision_request_max", c.RequestLimits.BulkDecisionRequestMax), + ), + ), slog.Bool("allow_direct_entitlements", c.AllowDirectEntitlements), slog.Bool("enforce_namespaced_entitlements", c.EnforceNamespacedEntitlements), ) diff --git a/service/authorization/v2/config_test.go b/service/authorization/v2/config_test.go index 2342f1d1d2..6d1b8e5f29 100644 --- a/service/authorization/v2/config_test.go +++ b/service/authorization/v2/config_test.go @@ -3,52 +3,112 @@ package authorization import ( "testing" + "github.com/creasty/defaults" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func Test_ValidateConfig_ValidConfig(t *testing.T) { - config := &Config{ - Cache: EntitlementPolicyCacheConfig{ - Enabled: true, - RefreshInterval: "30s", - }, - } + config := newConfigWithDefaults(t) + config.Cache.Enabled = true err := config.Validate() assert.NoError(t, err) } func Test_ValidateConfig_InvalidRefreshInterval(t *testing.T) { - config := &Config{ - Cache: EntitlementPolicyCacheConfig{ - Enabled: true, - RefreshInterval: "5ms", // Too short - }, - } + config := newConfigWithDefaults(t) + config.Cache.Enabled = true + config.Cache.RefreshInterval = "5ms" // Too short err := config.Validate() require.Error(t, err) - config = &Config{ - Cache: EntitlementPolicyCacheConfig{ - Enabled: true, - RefreshInterval: "2h", // Too long - }, - } + config = newConfigWithDefaults(t) + config.Cache.Enabled = true + config.Cache.RefreshInterval = "2h" // Too long err = config.Validate() require.Error(t, err) } func Test_ValidateConfig_DisabledCache(t *testing.T) { - config := &Config{ - Cache: EntitlementPolicyCacheConfig{ - Enabled: false, - RefreshInterval: "30s", - }, - } + config := newConfigWithDefaults(t) + config.Cache.Enabled = false err := config.Validate() assert.NoError(t, err) } + +func Test_ValidateConfig_DefaultRequestLimits(t *testing.T) { + config := newConfigWithDefaults(t) + + assert.Equal(t, 20, config.RequestLimits.ResourceAttributeValuesMax) + assert.Equal(t, 10, config.RequestLimits.EntityChainEntitiesMax) + assert.Equal(t, 50, config.RequestLimits.FulfillableObligationFqnsMax) + assert.Equal(t, 1000, config.RequestLimits.MultiResourceRequestMax) + assert.Equal(t, 200, config.RequestLimits.BulkDecisionRequestMax) +} + +func Test_ValidateConfig_InvalidRequestLimits(t *testing.T) { + cases := []struct { + name string + mutate func(*Config) + expectedErr string + }{ + { + name: "resource attribute values max must be positive", + mutate: func(config *Config) { + config.RequestLimits.ResourceAttributeValuesMax = 0 + }, + expectedErr: "resource_attribute_values_max [0] must be greater than 0", + }, + { + name: "entity chain entities max must be positive", + mutate: func(config *Config) { + config.RequestLimits.EntityChainEntitiesMax = 0 + }, + expectedErr: "entity_chain_entities_max [0] must be greater than 0", + }, + { + name: "fulfillable obligation fqns max can be zero but not negative", + mutate: func(config *Config) { + config.RequestLimits.FulfillableObligationFqnsMax = -1 + }, + expectedErr: "fulfillable_obligation_fqns_max [-1] must be greater than or equal to 0", + }, + { + name: "multi resource request max must be positive", + mutate: func(config *Config) { + config.RequestLimits.MultiResourceRequestMax = 0 + }, + expectedErr: "multi_resource_request_max [0] must be greater than 0", + }, + { + name: "bulk decision request max must be positive", + mutate: func(config *Config) { + config.RequestLimits.BulkDecisionRequestMax = 0 + }, + expectedErr: "bulk_decision_request_max [0] must be greater than 0", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + config := newConfigWithDefaults(t) + tc.mutate(config) + + err := config.Validate() + require.Error(t, err) + assert.Contains(t, err.Error(), tc.expectedErr) + }) + } +} + +func newConfigWithDefaults(t *testing.T) *Config { + t.Helper() + + config := &Config{} + require.NoError(t, defaults.Set(config)) + return config +} diff --git a/service/authorization/v2/validation.go b/service/authorization/v2/validation.go new file mode 100644 index 0000000000..07626f6b52 --- /dev/null +++ b/service/authorization/v2/validation.go @@ -0,0 +1,91 @@ +package authorization + +import ( + "fmt" + + "connectrpc.com/connect" + authzV2 "github.com/opentdf/platform/protocol/go/authorization/v2" +) + +func (as *Service) validateGetDecisionRequest(request *authzV2.GetDecisionRequest) error { + if err := as.validateEntityIdentifierRequestLimits(request.GetEntityIdentifier(), "entity_identifier"); err != nil { + return err + } + if err := as.validateResourceRequestLimits(request.GetResource(), "resource"); err != nil { + return err + } + return as.validateFulfillableObligationRequestLimits(request.GetFulfillableObligationFqns(), "fulfillable_obligation_fqns") +} + +func (as *Service) validateGetDecisionMultiResourceRequest(request *authzV2.GetDecisionMultiResourceRequest, prefix string) error { + if err := as.validateEntityIdentifierRequestLimits(request.GetEntityIdentifier(), prefix+"entity_identifier"); err != nil { + return err + } + + resources := request.GetResources() + if len(resources) > as.config.RequestLimits.MultiResourceRequestMax { + return limitExceededError(prefix+"resources", len(resources), as.config.RequestLimits.MultiResourceRequestMax) + } + for idx, resource := range resources { + if err := as.validateResourceRequestLimits(resource, fmt.Sprintf("%sresources[%d]", prefix, idx)); err != nil { + return err + } + } + + return as.validateFulfillableObligationRequestLimits(request.GetFulfillableObligationFqns(), prefix+"fulfillable_obligation_fqns") +} + +func (as *Service) validateGetDecisionBulkRequest(request *authzV2.GetDecisionBulkRequest) error { + decisionRequests := request.GetDecisionRequests() + if len(decisionRequests) > as.config.RequestLimits.BulkDecisionRequestMax { + return limitExceededError("decision_requests", len(decisionRequests), as.config.RequestLimits.BulkDecisionRequestMax) + } + + for idx, decisionRequest := range decisionRequests { + if err := as.validateGetDecisionMultiResourceRequest(decisionRequest, fmt.Sprintf("decision_requests[%d].", idx)); err != nil { + return err + } + } + + return nil +} + +func (as *Service) validateEntityIdentifierRequestLimits(entityIdentifier *authzV2.EntityIdentifier, path string) error { + entityChain := entityIdentifier.GetEntityChain() + if entityChain == nil { + return nil + } + + entities := entityChain.GetEntities() + if len(entities) > as.config.RequestLimits.EntityChainEntitiesMax { + return limitExceededError(path+".entity_chain.entities", len(entities), as.config.RequestLimits.EntityChainEntitiesMax) + } + + return nil +} + +func (as *Service) validateResourceRequestLimits(resource *authzV2.Resource, path string) error { + attributeValues := resource.GetAttributeValues() + if attributeValues == nil { + return nil + } + + fqns := attributeValues.GetFqns() + if len(fqns) > as.config.RequestLimits.ResourceAttributeValuesMax { + return limitExceededError(path+".attribute_values.fqns", len(fqns), as.config.RequestLimits.ResourceAttributeValuesMax) + } + + return nil +} + +func (as *Service) validateFulfillableObligationRequestLimits(fqns []string, path string) error { + if len(fqns) > as.config.RequestLimits.FulfillableObligationFqnsMax { + return limitExceededError(path, len(fqns), as.config.RequestLimits.FulfillableObligationFqnsMax) + } + + return nil +} + +func limitExceededError(path string, got int, limit int) error { + return connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("%s exceeds maximum count: got %d, max %d", path, got, limit)) +} diff --git a/service/authorization/v2/validation_test.go b/service/authorization/v2/validation_test.go new file mode 100644 index 0000000000..911e973bfd --- /dev/null +++ b/service/authorization/v2/validation_test.go @@ -0,0 +1,284 @@ +package authorization + +import ( + "context" + "strconv" + "testing" + + "connectrpc.com/connect" + authzV2 "github.com/opentdf/platform/protocol/go/authorization/v2" + "github.com/opentdf/platform/protocol/go/entity" + "github.com/opentdf/platform/service/logger" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" +) + +func Test_validateGetDecisionRequest_DefaultRequestLimits(t *testing.T) { + service := newValidationTestService(t, nil) + + cases := []struct { + name string + request *authzV2.GetDecisionRequest + expectedErr string + }{ + { + name: "entity chain entities", + request: newDecisionRequestWithEntityChainCount(11), + expectedErr: "entity_identifier.entity_chain.entities exceeds maximum count: got 11, max 10", + }, + { + name: "resource attribute values", + request: newDecisionRequestWithAttributeValueCount(21), + expectedErr: "resource.attribute_values.fqns exceeds maximum count: got 21, max 20", + }, + { + name: "fulfillable obligation fqns", + request: newDecisionRequestWithObligationCount(51), + expectedErr: "fulfillable_obligation_fqns exceeds maximum count: got 51, max 50", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := service.validateGetDecisionRequest(tc.request) + require.Error(t, err) + assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + assert.Contains(t, err.Error(), tc.expectedErr) + }) + } +} + +func Test_validateGetDecisionMultiResourceRequest_DefaultRequestLimit(t *testing.T) { + service := newValidationTestService(t, nil) + + err := service.validateGetDecisionMultiResourceRequest(newDecisionMultiResourceRequestWithResourceCount(1001), "") + require.Error(t, err) + assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + assert.Contains(t, err.Error(), "resources exceeds maximum count: got 1001, max 1000") +} + +func Test_validateGetDecisionBulkRequest_DefaultRequestLimit(t *testing.T) { + service := newValidationTestService(t, nil) + + err := service.validateGetDecisionBulkRequest(newDecisionBulkRequestWithDecisionCount(201)) + require.Error(t, err) + assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + assert.Contains(t, err.Error(), "decision_requests exceeds maximum count: got 201, max 200") +} + +func Test_validateGetDecisionBulkRequest_NestedLimitErrorIncludesPath(t *testing.T) { + service := newValidationTestService(t, nil) + + err := service.validateGetDecisionBulkRequest(&authzV2.GetDecisionBulkRequest{ + DecisionRequests: []*authzV2.GetDecisionMultiResourceRequest{ + newDecisionMultiResourceRequestWithAttributeValueCount(21), + }, + }) + require.Error(t, err) + assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + assert.Contains(t, err.Error(), "decision_requests[0].resources[0].attribute_values.fqns exceeds maximum count: got 21, max 20") +} + +func Test_validateDecisionRequests_UseCustomRequestLimits(t *testing.T) { + service := newValidationTestService(t, &RequestLimitsConfig{ + ResourceAttributeValuesMax: 21, + EntityChainEntitiesMax: 11, + FulfillableObligationFqnsMax: 51, + MultiResourceRequestMax: 1001, + BulkDecisionRequestMax: 201, + }) + + require.NoError(t, service.validateGetDecisionRequest(newDecisionRequestWithEntityChainCount(11))) + require.NoError(t, service.validateGetDecisionRequest(newDecisionRequestWithAttributeValueCount(21))) + require.NoError(t, service.validateGetDecisionRequest(newDecisionRequestWithObligationCount(51))) + require.NoError(t, service.validateGetDecisionMultiResourceRequest(newDecisionMultiResourceRequestWithResourceCount(1001), "")) + require.NoError(t, service.validateGetDecisionBulkRequest(newDecisionBulkRequestWithDecisionCount(201))) +} + +func Test_GetDecision_ReturnsInvalidArgumentForConfiguredLimit(t *testing.T) { + service := newHandlerTestService(t, nil) + + _, err := service.GetDecision(context.Background(), connect.NewRequest(newDecisionRequestWithAttributeValueCount(21))) + require.Error(t, err) + assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + assert.Contains(t, err.Error(), "resource.attribute_values.fqns exceeds maximum count: got 21, max 20") +} + +func Test_GetDecisionMultiResource_UsesCustomConfiguredLimit(t *testing.T) { + service := newHandlerTestService(t, func(config *Config) { + config.RequestLimits.MultiResourceRequestMax = 2 + }) + + _, err := service.GetDecisionMultiResource(context.Background(), connect.NewRequest(newDecisionMultiResourceRequestWithResourceCount(3))) + require.Error(t, err) + assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + assert.Contains(t, err.Error(), "resources exceeds maximum count: got 3, max 2") +} + +func Test_GetDecisionBulk_ReturnsInvalidArgumentForConfiguredLimit(t *testing.T) { + service := newHandlerTestService(t, func(config *Config) { + config.RequestLimits.BulkDecisionRequestMax = 1 + }) + + _, err := service.GetDecisionBulk(context.Background(), connect.NewRequest(newDecisionBulkRequestWithDecisionCount(2))) + require.Error(t, err) + assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + assert.Contains(t, err.Error(), "decision_requests exceeds maximum count: got 2, max 1") +} + +func newValidationTestService(t *testing.T, requestLimits *RequestLimitsConfig) *Service { + t.Helper() + + config := newConfigWithDefaults(t) + if requestLimits != nil { + config.RequestLimits = *requestLimits + } + require.NoError(t, config.Validate()) + + return &Service{config: config} +} + +func newHandlerTestService(t *testing.T, mutate func(*Config)) *Service { + t.Helper() + + config := newConfigWithDefaults(t) + if mutate != nil { + mutate(config) + } + require.NoError(t, config.Validate()) + + return &Service{ + config: config, + logger: logger.CreateTestLogger(), + Tracer: noop.NewTracerProvider().Tracer(""), + } +} + +func newDecisionRequestWithEntityChainCount(count int) *authzV2.GetDecisionRequest { + return &authzV2.GetDecisionRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_EntityChain{ + EntityChain: &entity.EntityChain{ + EphemeralId: "entity-chain", + Entities: newEntities(count), + }, + }, + }, + Action: sampleActionCreate, + Resource: &authzV2.Resource{ + Resource: &authzV2.Resource_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + } +} + +func newDecisionRequestWithAttributeValueCount(count int) *authzV2.GetDecisionRequest { + return &authzV2.GetDecisionRequest{ + EntityIdentifier: newTokenEntityIdentifier(), + Action: sampleActionCreate, + Resource: &authzV2.Resource{ + Resource: &authzV2.Resource_AttributeValues_{ + AttributeValues: &authzV2.Resource_AttributeValues{ + Fqns: newAttributeValueFQNs(count), + }, + }, + }, + } +} + +func newDecisionRequestWithObligationCount(count int) *authzV2.GetDecisionRequest { + return &authzV2.GetDecisionRequest{ + EntityIdentifier: newTokenEntityIdentifier(), + Action: sampleActionCreate, + Resource: &authzV2.Resource{ + Resource: &authzV2.Resource_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + FulfillableObligationFqns: newObligationFQNs(count), + } +} + +func newDecisionMultiResourceRequestWithAttributeValueCount(count int) *authzV2.GetDecisionMultiResourceRequest { + return &authzV2.GetDecisionMultiResourceRequest{ + EntityIdentifier: newTokenEntityIdentifier(), + Action: sampleActionCreate, + Resources: []*authzV2.Resource{ + { + Resource: &authzV2.Resource_AttributeValues_{ + AttributeValues: &authzV2.Resource_AttributeValues{ + Fqns: newAttributeValueFQNs(count), + }, + }, + }, + }, + } +} + +func newDecisionMultiResourceRequestWithResourceCount(count int) *authzV2.GetDecisionMultiResourceRequest { + resources := make([]*authzV2.Resource, count) + for i := range count { + resources[i] = &authzV2.Resource{ + Resource: &authzV2.Resource_AttributeValues_{ + AttributeValues: &authzV2.Resource_AttributeValues{ + Fqns: []string{sampleResourceFQN}, + }, + }, + } + } + + return &authzV2.GetDecisionMultiResourceRequest{ + EntityIdentifier: newTokenEntityIdentifier(), + Action: sampleActionCreate, + Resources: resources, + } +} + +func newDecisionBulkRequestWithDecisionCount(count int) *authzV2.GetDecisionBulkRequest { + requests := make([]*authzV2.GetDecisionMultiResourceRequest, count) + for i := range count { + requests[i] = newDecisionMultiResourceRequestWithResourceCount(1) + } + return &authzV2.GetDecisionBulkRequest{DecisionRequests: requests} +} + +func newTokenEntityIdentifier() *authzV2.EntityIdentifier { + return &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_Token{ + Token: &entity.Token{ + EphemeralId: "token-entity", + Jwt: "sample-jwt-token", + }, + }, + } +} + +func newEntities(count int) []*entity.Entity { + entities := make([]*entity.Entity, count) + for i := range count { + entities[i] = &entity.Entity{ + EphemeralId: "entity-" + strconv.Itoa(i), + EntityType: &entity.Entity_EmailAddress{EmailAddress: "test@test.com"}, + Category: entity.Entity_CATEGORY_SUBJECT, + } + } + return entities +} + +func newAttributeValueFQNs(count int) []string { + fqns := make([]string, count) + for i := range count { + fqns[i] = "https://example.com/attr/any_of_attr_name/value/val" + strconv.Itoa(i) + } + return fqns +} + +func newObligationFQNs(count int) []string { + fqns := make([]string, count) + for i := range count { + fqns[i] = "https://example.com/obl/drm/value/prevent_print_" + strconv.Itoa(i) + } + return fqns +} From 508ca58e94e55e78310d7f38c9c72f2d2bc142b2 Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Thu, 21 May 2026 10:56:59 -0700 Subject: [PATCH 2/7] fix(authz): enforce entitlement request limit Signed-off-by: jakedoublev --- service/authorization/v2/authorization.go | 4 +++ service/authorization/v2/validation.go | 4 +++ service/authorization/v2/validation_test.go | 33 +++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/service/authorization/v2/authorization.go b/service/authorization/v2/authorization.go index c2bdc21feb..da5a870ec0 100644 --- a/service/authorization/v2/authorization.go +++ b/service/authorization/v2/authorization.go @@ -139,6 +139,10 @@ func (as *Service) GetEntitlements(ctx context.Context, req *connect.Request[aut ctx, span := as.Start(ctx, "GetEntitlements") defer span.End() + if err := as.validateGetEntitlementsRequest(req.Msg); err != nil { + return nil, err + } + entityIdentifier := req.Msg.GetEntityIdentifier() withComprehensiveHierarchy := req.Msg.GetWithComprehensiveHierarchy() diff --git a/service/authorization/v2/validation.go b/service/authorization/v2/validation.go index 07626f6b52..9e294d6312 100644 --- a/service/authorization/v2/validation.go +++ b/service/authorization/v2/validation.go @@ -17,6 +17,10 @@ func (as *Service) validateGetDecisionRequest(request *authzV2.GetDecisionReques return as.validateFulfillableObligationRequestLimits(request.GetFulfillableObligationFqns(), "fulfillable_obligation_fqns") } +func (as *Service) validateGetEntitlementsRequest(request *authzV2.GetEntitlementsRequest) error { + return as.validateEntityIdentifierRequestLimits(request.GetEntityIdentifier(), "entity_identifier") +} + func (as *Service) validateGetDecisionMultiResourceRequest(request *authzV2.GetDecisionMultiResourceRequest, prefix string) error { if err := as.validateEntityIdentifierRequestLimits(request.GetEntityIdentifier(), prefix+"entity_identifier"); err != nil { return err diff --git a/service/authorization/v2/validation_test.go b/service/authorization/v2/validation_test.go index 911e973bfd..105435e535 100644 --- a/service/authorization/v2/validation_test.go +++ b/service/authorization/v2/validation_test.go @@ -49,6 +49,15 @@ func Test_validateGetDecisionRequest_DefaultRequestLimits(t *testing.T) { } } +func Test_validateGetEntitlementsRequest_DefaultRequestLimit(t *testing.T) { + service := newValidationTestService(t, nil) + + err := service.validateGetEntitlementsRequest(newEntitlementsRequestWithEntityChainCount(11)) + require.Error(t, err) + assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + assert.Contains(t, err.Error(), "entity_identifier.entity_chain.entities exceeds maximum count: got 11, max 10") +} + func Test_validateGetDecisionMultiResourceRequest_DefaultRequestLimit(t *testing.T) { service := newValidationTestService(t, nil) @@ -105,6 +114,17 @@ func Test_GetDecision_ReturnsInvalidArgumentForConfiguredLimit(t *testing.T) { assert.Contains(t, err.Error(), "resource.attribute_values.fqns exceeds maximum count: got 21, max 20") } +func Test_GetEntitlements_ReturnsInvalidArgumentForConfiguredLimit(t *testing.T) { + service := newHandlerTestService(t, func(config *Config) { + config.RequestLimits.EntityChainEntitiesMax = 1 + }) + + _, err := service.GetEntitlements(context.Background(), connect.NewRequest(newEntitlementsRequestWithEntityChainCount(2))) + require.Error(t, err) + assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + assert.Contains(t, err.Error(), "entity_identifier.entity_chain.entities exceeds maximum count: got 2, max 1") +} + func Test_GetDecisionMultiResource_UsesCustomConfiguredLimit(t *testing.T) { service := newHandlerTestService(t, func(config *Config) { config.RequestLimits.MultiResourceRequestMax = 2 @@ -174,6 +194,19 @@ func newDecisionRequestWithEntityChainCount(count int) *authzV2.GetDecisionReque } } +func newEntitlementsRequestWithEntityChainCount(count int) *authzV2.GetEntitlementsRequest { + return &authzV2.GetEntitlementsRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_EntityChain{ + EntityChain: &entity.EntityChain{ + EphemeralId: "entity-chain", + Entities: newEntities(count), + }, + }, + }, + } +} + func newDecisionRequestWithAttributeValueCount(count int) *authzV2.GetDecisionRequest { return &authzV2.GetDecisionRequest{ EntityIdentifier: newTokenEntityIdentifier(), From 751d2f566bcd3b2e2bacf458addde4643d638de9 Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Thu, 21 May 2026 11:49:32 -0700 Subject: [PATCH 3/7] fix(authz): clarify request limit config names --- docs/Configuring.md | 20 +++++------ service/authorization/v2/config.go | 40 ++++++++++----------- service/authorization/v2/config_test.go | 30 ++++++++-------- service/authorization/v2/validation.go | 20 +++++------ service/authorization/v2/validation_test.go | 16 ++++----- 5 files changed, 63 insertions(+), 63 deletions(-) diff --git a/docs/Configuring.md b/docs/Configuring.md index 2dbb4f30c0..f5232857c4 100644 --- a/docs/Configuring.md +++ b/docs/Configuring.md @@ -445,11 +445,11 @@ Root level key `authorization` | ------------------------------------------- | -------------------------------------------------------------- | ------- | --------------------- | | `entitlement_policy_cache.enabled` | Enable the entitlement policy cache | `false` | | | `entitlement_policy_cache.refresh_interval` | How often to refresh the entitlement policy cache (e.g. `30s`) | | | -| `request_limits.resource_attribute_values_max` | Maximum attribute value FQNs allowed in `resource.attribute_values.fqns` | `20` | | -| `request_limits.entity_chain_entities_max` | Maximum entities allowed in `entity_identifier.entity_chain.entities` | `10` | | -| `request_limits.fulfillable_obligation_fqns_max` | Maximum fulfillable obligation FQNs allowed per decision request | `50` | | -| `request_limits.multi_resource_request_max` | Maximum resources allowed in `GetDecisionMultiResourceRequest.resources` | `1000` | | -| `request_limits.bulk_decision_request_max` | Maximum decision requests allowed in `GetDecisionBulkRequest.decision_requests` | `200` | | +| `request_limits.resource_attribute_values_fqns_max` | Maximum attribute value FQNs allowed in Decision Requests | `20` | | +| `request_limits.entity_identifier_entity_chain_entities_max` | Maximum entities allowed in Decision Request entity chains | `10` | | +| `request_limits.decision_request_fulfillable_obligation_fqns_max` | Maximum fulfillable obligation FQNs allowed per Decision Request | `50` | | +| `request_limits.get_decision_multi_resource_resources_max` | Maximum resources allowed in `GetDecisionMultiResourceRequest` | `1000` | | +| `request_limits.get_decision_bulk_decision_requests_max` | Maximum decision requests allowed in `GetDecisionBulkRequest` | `200` | | #### Example: Authorization v1 @@ -470,11 +470,11 @@ services: enabled: false refresh_interval: 30s request_limits: - resource_attribute_values_max: 20 - entity_chain_entities_max: 10 - fulfillable_obligation_fqns_max: 50 - multi_resource_request_max: 1000 - bulk_decision_request_max: 200 + resource_attribute_values_fqns_max: 20 + entity_identifier_entity_chain_entities_max: 10 + decision_request_fulfillable_obligation_fqns_max: 50 + get_decision_multi_resource_resources_max: 1000 + get_decision_bulk_decision_requests_max: 200 ``` ### Entity Resolution diff --git a/service/authorization/v2/config.go b/service/authorization/v2/config.go index bfadec2829..31470bea62 100644 --- a/service/authorization/v2/config.go +++ b/service/authorization/v2/config.go @@ -14,28 +14,28 @@ type EntitlementPolicyCacheConfig struct { } type RequestLimitsConfig struct { - ResourceAttributeValuesMax int `mapstructure:"resource_attribute_values_max" json:"resource_attribute_values_max" default:"20"` - EntityChainEntitiesMax int `mapstructure:"entity_chain_entities_max" json:"entity_chain_entities_max" default:"10"` - FulfillableObligationFqnsMax int `mapstructure:"fulfillable_obligation_fqns_max" json:"fulfillable_obligation_fqns_max" default:"50"` - MultiResourceRequestMax int `mapstructure:"multi_resource_request_max" json:"multi_resource_request_max" default:"1000"` - BulkDecisionRequestMax int `mapstructure:"bulk_decision_request_max" json:"bulk_decision_request_max" default:"200"` + ResourceAttributeValuesFqnsMax int `mapstructure:"resource_attribute_values_fqns_max" json:"resource_attribute_values_fqns_max" default:"20"` + EntityIdentifierEntityChainEntitiesMax int `mapstructure:"entity_identifier_entity_chain_entities_max" json:"entity_identifier_entity_chain_entities_max" default:"10"` + DecisionRequestFulfillableObligationFqnsMax int `mapstructure:"decision_request_fulfillable_obligation_fqns_max" json:"decision_request_fulfillable_obligation_fqns_max" default:"50"` + GetDecisionMultiResourceResourcesMax int `mapstructure:"get_decision_multi_resource_resources_max" json:"get_decision_multi_resource_resources_max" default:"1000"` + GetDecisionBulkDecisionRequestsMax int `mapstructure:"get_decision_bulk_decision_requests_max" json:"get_decision_bulk_decision_requests_max" default:"200"` } func (c RequestLimitsConfig) Validate() error { - if c.ResourceAttributeValuesMax < 1 { - return fmt.Errorf("authorization request limit resource_attribute_values_max [%d] must be greater than 0", c.ResourceAttributeValuesMax) + if c.ResourceAttributeValuesFqnsMax < 1 { + return fmt.Errorf("authorization request limit resource_attribute_values_fqns_max [%d] must be greater than 0", c.ResourceAttributeValuesFqnsMax) } - if c.EntityChainEntitiesMax < 1 { - return fmt.Errorf("authorization request limit entity_chain_entities_max [%d] must be greater than 0", c.EntityChainEntitiesMax) + if c.EntityIdentifierEntityChainEntitiesMax < 1 { + return fmt.Errorf("authorization request limit entity_identifier_entity_chain_entities_max [%d] must be greater than 0", c.EntityIdentifierEntityChainEntitiesMax) } - if c.FulfillableObligationFqnsMax < 0 { - return fmt.Errorf("authorization request limit fulfillable_obligation_fqns_max [%d] must be greater than or equal to 0", c.FulfillableObligationFqnsMax) + if c.DecisionRequestFulfillableObligationFqnsMax < 0 { + return fmt.Errorf("authorization request limit decision_request_fulfillable_obligation_fqns_max [%d] must be greater than or equal to 0", c.DecisionRequestFulfillableObligationFqnsMax) } - if c.MultiResourceRequestMax < 1 { - return fmt.Errorf("authorization request limit multi_resource_request_max [%d] must be greater than 0", c.MultiResourceRequestMax) + if c.GetDecisionMultiResourceResourcesMax < 1 { + return fmt.Errorf("authorization request limit get_decision_multi_resource_resources_max [%d] must be greater than 0", c.GetDecisionMultiResourceResourcesMax) } - if c.BulkDecisionRequestMax < 1 { - return fmt.Errorf("authorization request limit bulk_decision_request_max [%d] must be greater than 0", c.BulkDecisionRequestMax) + if c.GetDecisionBulkDecisionRequestsMax < 1 { + return fmt.Errorf("authorization request limit get_decision_bulk_decision_requests_max [%d] must be greater than 0", c.GetDecisionBulkDecisionRequestsMax) } return nil } @@ -92,11 +92,11 @@ func (c *Config) LogValue() slog.Value { ), slog.Any("request_limits", slog.GroupValue( - slog.Int("resource_attribute_values_max", c.RequestLimits.ResourceAttributeValuesMax), - slog.Int("entity_chain_entities_max", c.RequestLimits.EntityChainEntitiesMax), - slog.Int("fulfillable_obligation_fqns_max", c.RequestLimits.FulfillableObligationFqnsMax), - slog.Int("multi_resource_request_max", c.RequestLimits.MultiResourceRequestMax), - slog.Int("bulk_decision_request_max", c.RequestLimits.BulkDecisionRequestMax), + slog.Int("resource_attribute_values_fqns_max", c.RequestLimits.ResourceAttributeValuesFqnsMax), + slog.Int("entity_identifier_entity_chain_entities_max", c.RequestLimits.EntityIdentifierEntityChainEntitiesMax), + slog.Int("decision_request_fulfillable_obligation_fqns_max", c.RequestLimits.DecisionRequestFulfillableObligationFqnsMax), + slog.Int("get_decision_multi_resource_resources_max", c.RequestLimits.GetDecisionMultiResourceResourcesMax), + slog.Int("get_decision_bulk_decision_requests_max", c.RequestLimits.GetDecisionBulkDecisionRequestsMax), ), ), slog.Bool("allow_direct_entitlements", c.AllowDirectEntitlements), diff --git a/service/authorization/v2/config_test.go b/service/authorization/v2/config_test.go index 6d1b8e5f29..e491e4ecfc 100644 --- a/service/authorization/v2/config_test.go +++ b/service/authorization/v2/config_test.go @@ -43,11 +43,11 @@ func Test_ValidateConfig_DisabledCache(t *testing.T) { func Test_ValidateConfig_DefaultRequestLimits(t *testing.T) { config := newConfigWithDefaults(t) - assert.Equal(t, 20, config.RequestLimits.ResourceAttributeValuesMax) - assert.Equal(t, 10, config.RequestLimits.EntityChainEntitiesMax) - assert.Equal(t, 50, config.RequestLimits.FulfillableObligationFqnsMax) - assert.Equal(t, 1000, config.RequestLimits.MultiResourceRequestMax) - assert.Equal(t, 200, config.RequestLimits.BulkDecisionRequestMax) + assert.Equal(t, 20, config.RequestLimits.ResourceAttributeValuesFqnsMax) + assert.Equal(t, 10, config.RequestLimits.EntityIdentifierEntityChainEntitiesMax) + assert.Equal(t, 50, config.RequestLimits.DecisionRequestFulfillableObligationFqnsMax) + assert.Equal(t, 1000, config.RequestLimits.GetDecisionMultiResourceResourcesMax) + assert.Equal(t, 200, config.RequestLimits.GetDecisionBulkDecisionRequestsMax) } func Test_ValidateConfig_InvalidRequestLimits(t *testing.T) { @@ -59,37 +59,37 @@ func Test_ValidateConfig_InvalidRequestLimits(t *testing.T) { { name: "resource attribute values max must be positive", mutate: func(config *Config) { - config.RequestLimits.ResourceAttributeValuesMax = 0 + config.RequestLimits.ResourceAttributeValuesFqnsMax = 0 }, - expectedErr: "resource_attribute_values_max [0] must be greater than 0", + expectedErr: "resource_attribute_values_fqns_max [0] must be greater than 0", }, { name: "entity chain entities max must be positive", mutate: func(config *Config) { - config.RequestLimits.EntityChainEntitiesMax = 0 + config.RequestLimits.EntityIdentifierEntityChainEntitiesMax = 0 }, - expectedErr: "entity_chain_entities_max [0] must be greater than 0", + expectedErr: "entity_identifier_entity_chain_entities_max [0] must be greater than 0", }, { name: "fulfillable obligation fqns max can be zero but not negative", mutate: func(config *Config) { - config.RequestLimits.FulfillableObligationFqnsMax = -1 + config.RequestLimits.DecisionRequestFulfillableObligationFqnsMax = -1 }, - expectedErr: "fulfillable_obligation_fqns_max [-1] must be greater than or equal to 0", + expectedErr: "decision_request_fulfillable_obligation_fqns_max [-1] must be greater than or equal to 0", }, { name: "multi resource request max must be positive", mutate: func(config *Config) { - config.RequestLimits.MultiResourceRequestMax = 0 + config.RequestLimits.GetDecisionMultiResourceResourcesMax = 0 }, - expectedErr: "multi_resource_request_max [0] must be greater than 0", + expectedErr: "get_decision_multi_resource_resources_max [0] must be greater than 0", }, { name: "bulk decision request max must be positive", mutate: func(config *Config) { - config.RequestLimits.BulkDecisionRequestMax = 0 + config.RequestLimits.GetDecisionBulkDecisionRequestsMax = 0 }, - expectedErr: "bulk_decision_request_max [0] must be greater than 0", + expectedErr: "get_decision_bulk_decision_requests_max [0] must be greater than 0", }, } diff --git a/service/authorization/v2/validation.go b/service/authorization/v2/validation.go index 9e294d6312..37e22aef0b 100644 --- a/service/authorization/v2/validation.go +++ b/service/authorization/v2/validation.go @@ -27,8 +27,8 @@ func (as *Service) validateGetDecisionMultiResourceRequest(request *authzV2.GetD } resources := request.GetResources() - if len(resources) > as.config.RequestLimits.MultiResourceRequestMax { - return limitExceededError(prefix+"resources", len(resources), as.config.RequestLimits.MultiResourceRequestMax) + if len(resources) > as.config.RequestLimits.GetDecisionMultiResourceResourcesMax { + return limitExceededError(prefix+"resources", len(resources), as.config.RequestLimits.GetDecisionMultiResourceResourcesMax) } for idx, resource := range resources { if err := as.validateResourceRequestLimits(resource, fmt.Sprintf("%sresources[%d]", prefix, idx)); err != nil { @@ -41,8 +41,8 @@ func (as *Service) validateGetDecisionMultiResourceRequest(request *authzV2.GetD func (as *Service) validateGetDecisionBulkRequest(request *authzV2.GetDecisionBulkRequest) error { decisionRequests := request.GetDecisionRequests() - if len(decisionRequests) > as.config.RequestLimits.BulkDecisionRequestMax { - return limitExceededError("decision_requests", len(decisionRequests), as.config.RequestLimits.BulkDecisionRequestMax) + if len(decisionRequests) > as.config.RequestLimits.GetDecisionBulkDecisionRequestsMax { + return limitExceededError("decision_requests", len(decisionRequests), as.config.RequestLimits.GetDecisionBulkDecisionRequestsMax) } for idx, decisionRequest := range decisionRequests { @@ -61,8 +61,8 @@ func (as *Service) validateEntityIdentifierRequestLimits(entityIdentifier *authz } entities := entityChain.GetEntities() - if len(entities) > as.config.RequestLimits.EntityChainEntitiesMax { - return limitExceededError(path+".entity_chain.entities", len(entities), as.config.RequestLimits.EntityChainEntitiesMax) + if len(entities) > as.config.RequestLimits.EntityIdentifierEntityChainEntitiesMax { + return limitExceededError(path+".entity_chain.entities", len(entities), as.config.RequestLimits.EntityIdentifierEntityChainEntitiesMax) } return nil @@ -75,16 +75,16 @@ func (as *Service) validateResourceRequestLimits(resource *authzV2.Resource, pat } fqns := attributeValues.GetFqns() - if len(fqns) > as.config.RequestLimits.ResourceAttributeValuesMax { - return limitExceededError(path+".attribute_values.fqns", len(fqns), as.config.RequestLimits.ResourceAttributeValuesMax) + if len(fqns) > as.config.RequestLimits.ResourceAttributeValuesFqnsMax { + return limitExceededError(path+".attribute_values.fqns", len(fqns), as.config.RequestLimits.ResourceAttributeValuesFqnsMax) } return nil } func (as *Service) validateFulfillableObligationRequestLimits(fqns []string, path string) error { - if len(fqns) > as.config.RequestLimits.FulfillableObligationFqnsMax { - return limitExceededError(path, len(fqns), as.config.RequestLimits.FulfillableObligationFqnsMax) + if len(fqns) > as.config.RequestLimits.DecisionRequestFulfillableObligationFqnsMax { + return limitExceededError(path, len(fqns), as.config.RequestLimits.DecisionRequestFulfillableObligationFqnsMax) } return nil diff --git a/service/authorization/v2/validation_test.go b/service/authorization/v2/validation_test.go index 105435e535..2855b84749 100644 --- a/service/authorization/v2/validation_test.go +++ b/service/authorization/v2/validation_test.go @@ -91,11 +91,11 @@ func Test_validateGetDecisionBulkRequest_NestedLimitErrorIncludesPath(t *testing func Test_validateDecisionRequests_UseCustomRequestLimits(t *testing.T) { service := newValidationTestService(t, &RequestLimitsConfig{ - ResourceAttributeValuesMax: 21, - EntityChainEntitiesMax: 11, - FulfillableObligationFqnsMax: 51, - MultiResourceRequestMax: 1001, - BulkDecisionRequestMax: 201, + ResourceAttributeValuesFqnsMax: 21, + EntityIdentifierEntityChainEntitiesMax: 11, + DecisionRequestFulfillableObligationFqnsMax: 51, + GetDecisionMultiResourceResourcesMax: 1001, + GetDecisionBulkDecisionRequestsMax: 201, }) require.NoError(t, service.validateGetDecisionRequest(newDecisionRequestWithEntityChainCount(11))) @@ -116,7 +116,7 @@ func Test_GetDecision_ReturnsInvalidArgumentForConfiguredLimit(t *testing.T) { func Test_GetEntitlements_ReturnsInvalidArgumentForConfiguredLimit(t *testing.T) { service := newHandlerTestService(t, func(config *Config) { - config.RequestLimits.EntityChainEntitiesMax = 1 + config.RequestLimits.EntityIdentifierEntityChainEntitiesMax = 1 }) _, err := service.GetEntitlements(context.Background(), connect.NewRequest(newEntitlementsRequestWithEntityChainCount(2))) @@ -127,7 +127,7 @@ func Test_GetEntitlements_ReturnsInvalidArgumentForConfiguredLimit(t *testing.T) func Test_GetDecisionMultiResource_UsesCustomConfiguredLimit(t *testing.T) { service := newHandlerTestService(t, func(config *Config) { - config.RequestLimits.MultiResourceRequestMax = 2 + config.RequestLimits.GetDecisionMultiResourceResourcesMax = 2 }) _, err := service.GetDecisionMultiResource(context.Background(), connect.NewRequest(newDecisionMultiResourceRequestWithResourceCount(3))) @@ -138,7 +138,7 @@ func Test_GetDecisionMultiResource_UsesCustomConfiguredLimit(t *testing.T) { func Test_GetDecisionBulk_ReturnsInvalidArgumentForConfiguredLimit(t *testing.T) { service := newHandlerTestService(t, func(config *Config) { - config.RequestLimits.BulkDecisionRequestMax = 1 + config.RequestLimits.GetDecisionBulkDecisionRequestsMax = 1 }) _, err := service.GetDecisionBulk(context.Background(), connect.NewRequest(newDecisionBulkRequestWithDecisionCount(2))) From ef6a44ce11f13ae34dc1f1e92fabcdcad31a9a5b Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Thu, 21 May 2026 12:18:25 -0700 Subject: [PATCH 4/7] fix(authz): require positive obligation limit and add boundary tests Make DecisionRequestFulfillableObligationFqnsMax require > 0 consistent with all other request limits. Add boundary tests verifying exactly-at-limit values pass validation. Signed-off-by: jakedoublev --- service/authorization/v2/config.go | 4 ++-- service/authorization/v2/config_test.go | 6 ++--- service/authorization/v2/validation_test.go | 26 +++++++++++++++++++++ 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/service/authorization/v2/config.go b/service/authorization/v2/config.go index 31470bea62..e21f6c7920 100644 --- a/service/authorization/v2/config.go +++ b/service/authorization/v2/config.go @@ -28,8 +28,8 @@ func (c RequestLimitsConfig) Validate() error { if c.EntityIdentifierEntityChainEntitiesMax < 1 { return fmt.Errorf("authorization request limit entity_identifier_entity_chain_entities_max [%d] must be greater than 0", c.EntityIdentifierEntityChainEntitiesMax) } - if c.DecisionRequestFulfillableObligationFqnsMax < 0 { - return fmt.Errorf("authorization request limit decision_request_fulfillable_obligation_fqns_max [%d] must be greater than or equal to 0", c.DecisionRequestFulfillableObligationFqnsMax) + if c.DecisionRequestFulfillableObligationFqnsMax < 1 { + return fmt.Errorf("authorization request limit decision_request_fulfillable_obligation_fqns_max [%d] must be greater than 0", c.DecisionRequestFulfillableObligationFqnsMax) } if c.GetDecisionMultiResourceResourcesMax < 1 { return fmt.Errorf("authorization request limit get_decision_multi_resource_resources_max [%d] must be greater than 0", c.GetDecisionMultiResourceResourcesMax) diff --git a/service/authorization/v2/config_test.go b/service/authorization/v2/config_test.go index e491e4ecfc..97e878ebcc 100644 --- a/service/authorization/v2/config_test.go +++ b/service/authorization/v2/config_test.go @@ -71,11 +71,11 @@ func Test_ValidateConfig_InvalidRequestLimits(t *testing.T) { expectedErr: "entity_identifier_entity_chain_entities_max [0] must be greater than 0", }, { - name: "fulfillable obligation fqns max can be zero but not negative", + name: "fulfillable obligation fqns max must be positive", mutate: func(config *Config) { - config.RequestLimits.DecisionRequestFulfillableObligationFqnsMax = -1 + config.RequestLimits.DecisionRequestFulfillableObligationFqnsMax = 0 }, - expectedErr: "decision_request_fulfillable_obligation_fqns_max [-1] must be greater than or equal to 0", + expectedErr: "decision_request_fulfillable_obligation_fqns_max [0] must be greater than 0", }, { name: "multi resource request max must be positive", diff --git a/service/authorization/v2/validation_test.go b/service/authorization/v2/validation_test.go index 2855b84749..3e1aa3bafc 100644 --- a/service/authorization/v2/validation_test.go +++ b/service/authorization/v2/validation_test.go @@ -89,6 +89,32 @@ func Test_validateGetDecisionBulkRequest_NestedLimitErrorIncludesPath(t *testing assert.Contains(t, err.Error(), "decision_requests[0].resources[0].attribute_values.fqns exceeds maximum count: got 21, max 20") } +func Test_validateGetDecisionRequest_ExactlyAtDefaultLimitPasses(t *testing.T) { + service := newValidationTestService(t, nil) + + require.NoError(t, service.validateGetDecisionRequest(newDecisionRequestWithEntityChainCount(10))) + require.NoError(t, service.validateGetDecisionRequest(newDecisionRequestWithAttributeValueCount(20))) + require.NoError(t, service.validateGetDecisionRequest(newDecisionRequestWithObligationCount(50))) +} + +func Test_validateGetEntitlementsRequest_ExactlyAtDefaultLimitPasses(t *testing.T) { + service := newValidationTestService(t, nil) + + require.NoError(t, service.validateGetEntitlementsRequest(newEntitlementsRequestWithEntityChainCount(10))) +} + +func Test_validateGetDecisionMultiResourceRequest_ExactlyAtDefaultLimitPasses(t *testing.T) { + service := newValidationTestService(t, nil) + + require.NoError(t, service.validateGetDecisionMultiResourceRequest(newDecisionMultiResourceRequestWithResourceCount(1000), "")) +} + +func Test_validateGetDecisionBulkRequest_ExactlyAtDefaultLimitPasses(t *testing.T) { + service := newValidationTestService(t, nil) + + require.NoError(t, service.validateGetDecisionBulkRequest(newDecisionBulkRequestWithDecisionCount(200))) +} + func Test_validateDecisionRequests_UseCustomRequestLimits(t *testing.T) { service := newValidationTestService(t, &RequestLimitsConfig{ ResourceAttributeValuesFqnsMax: 21, From a6a155c316d91901391108c972cb1edbc772bcda Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Thu, 21 May 2026 13:30:26 -0700 Subject: [PATCH 5/7] refactor(authz): use sentinel error for request limit config validation Add ErrInvalidRequestLimitConfig sentinel error and extract requestLimitConfigError helper to reduce duplication. Also replace context.Background() with t.Context() in validation tests. Signed-off-by: jakedoublev --- service/authorization/v2/config.go | 17 ++++++++++++----- service/authorization/v2/config_test.go | 2 +- service/authorization/v2/validation_test.go | 9 ++++----- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/service/authorization/v2/config.go b/service/authorization/v2/config.go index e21f6c7920..f966681c21 100644 --- a/service/authorization/v2/config.go +++ b/service/authorization/v2/config.go @@ -1,11 +1,14 @@ package authorization import ( + "errors" "fmt" "log/slog" "time" ) +var ErrInvalidRequestLimitConfig = errors.New("invalid request limit configuration") + // Manage config for EntitlementPolicyCache: attributes, subject mappings, and registered resources // Default: caching disabled, and if enabled, refresh interval defaulted to 30 seconds. type EntitlementPolicyCacheConfig struct { @@ -23,23 +26,27 @@ type RequestLimitsConfig struct { func (c RequestLimitsConfig) Validate() error { if c.ResourceAttributeValuesFqnsMax < 1 { - return fmt.Errorf("authorization request limit resource_attribute_values_fqns_max [%d] must be greater than 0", c.ResourceAttributeValuesFqnsMax) + return requestLimitConfigError("resource_attribute_values_fqns_max", c.ResourceAttributeValuesFqnsMax) } if c.EntityIdentifierEntityChainEntitiesMax < 1 { - return fmt.Errorf("authorization request limit entity_identifier_entity_chain_entities_max [%d] must be greater than 0", c.EntityIdentifierEntityChainEntitiesMax) + return requestLimitConfigError("entity_identifier_entity_chain_entities_max", c.EntityIdentifierEntityChainEntitiesMax) } if c.DecisionRequestFulfillableObligationFqnsMax < 1 { - return fmt.Errorf("authorization request limit decision_request_fulfillable_obligation_fqns_max [%d] must be greater than 0", c.DecisionRequestFulfillableObligationFqnsMax) + return requestLimitConfigError("decision_request_fulfillable_obligation_fqns_max", c.DecisionRequestFulfillableObligationFqnsMax) } if c.GetDecisionMultiResourceResourcesMax < 1 { - return fmt.Errorf("authorization request limit get_decision_multi_resource_resources_max [%d] must be greater than 0", c.GetDecisionMultiResourceResourcesMax) + return requestLimitConfigError("get_decision_multi_resource_resources_max", c.GetDecisionMultiResourceResourcesMax) } if c.GetDecisionBulkDecisionRequestsMax < 1 { - return fmt.Errorf("authorization request limit get_decision_bulk_decision_requests_max [%d] must be greater than 0", c.GetDecisionBulkDecisionRequestsMax) + return requestLimitConfigError("get_decision_bulk_decision_requests_max", c.GetDecisionBulkDecisionRequestsMax) } return nil } +func requestLimitConfigError(name string, value int) error { + return fmt.Errorf("%s [%d] must be greater than 0: %w", name, value, ErrInvalidRequestLimitConfig) +} + type Config struct { Cache EntitlementPolicyCacheConfig `mapstructure:"entitlement_policy_cache" json:"entitlement_policy_cache"` RequestLimits RequestLimitsConfig `mapstructure:"request_limits" json:"request_limits"` diff --git a/service/authorization/v2/config_test.go b/service/authorization/v2/config_test.go index 97e878ebcc..54ff96b683 100644 --- a/service/authorization/v2/config_test.go +++ b/service/authorization/v2/config_test.go @@ -99,7 +99,7 @@ func Test_ValidateConfig_InvalidRequestLimits(t *testing.T) { tc.mutate(config) err := config.Validate() - require.Error(t, err) + require.ErrorIs(t, err, ErrInvalidRequestLimitConfig) assert.Contains(t, err.Error(), tc.expectedErr) }) } diff --git a/service/authorization/v2/validation_test.go b/service/authorization/v2/validation_test.go index 3e1aa3bafc..dbc388af9d 100644 --- a/service/authorization/v2/validation_test.go +++ b/service/authorization/v2/validation_test.go @@ -1,7 +1,6 @@ package authorization import ( - "context" "strconv" "testing" @@ -134,7 +133,7 @@ func Test_validateDecisionRequests_UseCustomRequestLimits(t *testing.T) { func Test_GetDecision_ReturnsInvalidArgumentForConfiguredLimit(t *testing.T) { service := newHandlerTestService(t, nil) - _, err := service.GetDecision(context.Background(), connect.NewRequest(newDecisionRequestWithAttributeValueCount(21))) + _, err := service.GetDecision(t.Context(), connect.NewRequest(newDecisionRequestWithAttributeValueCount(21))) require.Error(t, err) assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) assert.Contains(t, err.Error(), "resource.attribute_values.fqns exceeds maximum count: got 21, max 20") @@ -145,7 +144,7 @@ func Test_GetEntitlements_ReturnsInvalidArgumentForConfiguredLimit(t *testing.T) config.RequestLimits.EntityIdentifierEntityChainEntitiesMax = 1 }) - _, err := service.GetEntitlements(context.Background(), connect.NewRequest(newEntitlementsRequestWithEntityChainCount(2))) + _, err := service.GetEntitlements(t.Context(), connect.NewRequest(newEntitlementsRequestWithEntityChainCount(2))) require.Error(t, err) assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) assert.Contains(t, err.Error(), "entity_identifier.entity_chain.entities exceeds maximum count: got 2, max 1") @@ -156,7 +155,7 @@ func Test_GetDecisionMultiResource_UsesCustomConfiguredLimit(t *testing.T) { config.RequestLimits.GetDecisionMultiResourceResourcesMax = 2 }) - _, err := service.GetDecisionMultiResource(context.Background(), connect.NewRequest(newDecisionMultiResourceRequestWithResourceCount(3))) + _, err := service.GetDecisionMultiResource(t.Context(), connect.NewRequest(newDecisionMultiResourceRequestWithResourceCount(3))) require.Error(t, err) assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) assert.Contains(t, err.Error(), "resources exceeds maximum count: got 3, max 2") @@ -167,7 +166,7 @@ func Test_GetDecisionBulk_ReturnsInvalidArgumentForConfiguredLimit(t *testing.T) config.RequestLimits.GetDecisionBulkDecisionRequestsMax = 1 }) - _, err := service.GetDecisionBulk(context.Background(), connect.NewRequest(newDecisionBulkRequestWithDecisionCount(2))) + _, err := service.GetDecisionBulk(t.Context(), connect.NewRequest(newDecisionBulkRequestWithDecisionCount(2))) require.Error(t, err) assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) assert.Contains(t, err.Error(), "decision_requests exceeds maximum count: got 2, max 1") From a0a6004e4b8e6d88e193773be45c5ee6da6ef61e Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Thu, 21 May 2026 13:50:39 -0700 Subject: [PATCH 6/7] test(authz): cover multi-resource obligation FQN limit validation Add proto and service-side tests for FulfillableObligationFqns on GetDecisionMultiResourceRequest, which was previously untested. Signed-off-by: jakedoublev --- .../authorization/v2/authorization_test.go | 28 +++++++++++ service/authorization/v2/validation_test.go | 48 +++++++++++++++++-- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/service/authorization/v2/authorization_test.go b/service/authorization/v2/authorization_test.go index 7ca65eedad..f495dca5fe 100644 --- a/service/authorization/v2/authorization_test.go +++ b/service/authorization/v2/authorization_test.go @@ -973,6 +973,34 @@ func Test_GetDecisionMultiResourceRequest_Succeeds(t *testing.T) { } } +func Test_GetDecisionMultiResourceRequest_ProtoValidationAllowsManyObligations(t *testing.T) { + v := getValidator() + + fiftyOneObligations := make([]string, 51) + for i := range 51 { + fiftyOneObligations[i] = sampleObligationValueFQN + } + + req := &authzV2.GetDecisionMultiResourceRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + Action: sampleActionCreate, + Resources: []*authzV2.Resource{ + { + Resource: &authzV2.Resource_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + }, + FulfillableObligationFqns: fiftyOneObligations, + } + err := v.Validate(req) + require.NoError(t, err, "validation should allow more than 50 obligations so the service can enforce configured limits") +} + func Test_GetDecisionMultiResourceRequest_ProtoValidationAllowsManyResources(t *testing.T) { v := getValidator() upperBoundLimit := 1000 diff --git a/service/authorization/v2/validation_test.go b/service/authorization/v2/validation_test.go index dbc388af9d..1448ba42ba 100644 --- a/service/authorization/v2/validation_test.go +++ b/service/authorization/v2/validation_test.go @@ -57,13 +57,34 @@ func Test_validateGetEntitlementsRequest_DefaultRequestLimit(t *testing.T) { assert.Contains(t, err.Error(), "entity_identifier.entity_chain.entities exceeds maximum count: got 11, max 10") } -func Test_validateGetDecisionMultiResourceRequest_DefaultRequestLimit(t *testing.T) { +func Test_validateGetDecisionMultiResourceRequest_DefaultRequestLimits(t *testing.T) { service := newValidationTestService(t, nil) - err := service.validateGetDecisionMultiResourceRequest(newDecisionMultiResourceRequestWithResourceCount(1001), "") - require.Error(t, err) - assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) - assert.Contains(t, err.Error(), "resources exceeds maximum count: got 1001, max 1000") + cases := []struct { + name string + request *authzV2.GetDecisionMultiResourceRequest + expectedErr string + }{ + { + name: "resources", + request: newDecisionMultiResourceRequestWithResourceCount(1001), + expectedErr: "resources exceeds maximum count: got 1001, max 1000", + }, + { + name: "fulfillable obligation fqns", + request: newDecisionMultiResourceRequestWithObligationCount(51), + expectedErr: "fulfillable_obligation_fqns exceeds maximum count: got 51, max 50", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := service.validateGetDecisionMultiResourceRequest(tc.request, "") + require.Error(t, err) + assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + assert.Contains(t, err.Error(), tc.expectedErr) + }) + } } func Test_validateGetDecisionBulkRequest_DefaultRequestLimit(t *testing.T) { @@ -106,6 +127,7 @@ func Test_validateGetDecisionMultiResourceRequest_ExactlyAtDefaultLimitPasses(t service := newValidationTestService(t, nil) require.NoError(t, service.validateGetDecisionMultiResourceRequest(newDecisionMultiResourceRequestWithResourceCount(1000), "")) + require.NoError(t, service.validateGetDecisionMultiResourceRequest(newDecisionMultiResourceRequestWithObligationCount(50), "")) } func Test_validateGetDecisionBulkRequest_ExactlyAtDefaultLimitPasses(t *testing.T) { @@ -127,6 +149,7 @@ func Test_validateDecisionRequests_UseCustomRequestLimits(t *testing.T) { require.NoError(t, service.validateGetDecisionRequest(newDecisionRequestWithAttributeValueCount(21))) require.NoError(t, service.validateGetDecisionRequest(newDecisionRequestWithObligationCount(51))) require.NoError(t, service.validateGetDecisionMultiResourceRequest(newDecisionMultiResourceRequestWithResourceCount(1001), "")) + require.NoError(t, service.validateGetDecisionMultiResourceRequest(newDecisionMultiResourceRequestWithObligationCount(51), "")) require.NoError(t, service.validateGetDecisionBulkRequest(newDecisionBulkRequestWithDecisionCount(201))) } @@ -275,6 +298,21 @@ func newDecisionMultiResourceRequestWithAttributeValueCount(count int) *authzV2. } } +func newDecisionMultiResourceRequestWithObligationCount(count int) *authzV2.GetDecisionMultiResourceRequest { + return &authzV2.GetDecisionMultiResourceRequest{ + EntityIdentifier: newTokenEntityIdentifier(), + Action: sampleActionCreate, + Resources: []*authzV2.Resource{ + { + Resource: &authzV2.Resource_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + }, + FulfillableObligationFqns: newObligationFQNs(count), + } +} + func newDecisionMultiResourceRequestWithResourceCount(count int) *authzV2.GetDecisionMultiResourceRequest { resources := make([]*authzV2.Resource, count) for i := range count { From 48ba271d2ed31bf35d1d37c5d1caef564c586270 Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Wed, 10 Jun 2026 10:23:55 -0700 Subject: [PATCH 7/7] test fixes --- .../authorization/v2/authorization_test.go | 2 +- service/authorization/v2/validation_test.go | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/service/authorization/v2/authorization_test.go b/service/authorization/v2/authorization_test.go index f495dca5fe..cb6075c6cd 100644 --- a/service/authorization/v2/authorization_test.go +++ b/service/authorization/v2/authorization_test.go @@ -435,7 +435,7 @@ func Test_EntityIdentifier_ProtoValidationAllowsManyChainedEntities(t *testing.T Category: entity.Entity_CATEGORY_SUBJECT, }) err = v.Validate(entityIdentifier) - require.NoError(t, err, "validation should allow more than 10 entities in chain so the service can enforce configured limits") + require.NoError(t, err) } func Test_Resource_ProtoValidationAllowsManyAttributeValues(t *testing.T) { diff --git a/service/authorization/v2/validation_test.go b/service/authorization/v2/validation_test.go index 1448ba42ba..6d102e10fb 100644 --- a/service/authorization/v2/validation_test.go +++ b/service/authorization/v2/validation_test.go @@ -65,6 +65,11 @@ func Test_validateGetDecisionMultiResourceRequest_DefaultRequestLimits(t *testin request *authzV2.GetDecisionMultiResourceRequest expectedErr string }{ + { + name: "entity chain entities", + request: newDecisionMultiResourceRequestWithEntityChainCount(11), + expectedErr: "entity_identifier.entity_chain.entities exceeds maximum count: got 11, max 10", + }, { name: "resources", request: newDecisionMultiResourceRequestWithResourceCount(1001), @@ -126,6 +131,7 @@ func Test_validateGetEntitlementsRequest_ExactlyAtDefaultLimitPasses(t *testing. func Test_validateGetDecisionMultiResourceRequest_ExactlyAtDefaultLimitPasses(t *testing.T) { service := newValidationTestService(t, nil) + require.NoError(t, service.validateGetDecisionMultiResourceRequest(newDecisionMultiResourceRequestWithEntityChainCount(10), "")) require.NoError(t, service.validateGetDecisionMultiResourceRequest(newDecisionMultiResourceRequestWithResourceCount(1000), "")) require.NoError(t, service.validateGetDecisionMultiResourceRequest(newDecisionMultiResourceRequestWithObligationCount(50), "")) } @@ -282,6 +288,27 @@ func newDecisionRequestWithObligationCount(count int) *authzV2.GetDecisionReques } } +func newDecisionMultiResourceRequestWithEntityChainCount(count int) *authzV2.GetDecisionMultiResourceRequest { + return &authzV2.GetDecisionMultiResourceRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_EntityChain{ + EntityChain: &entity.EntityChain{ + EphemeralId: "entity-chain", + Entities: newEntities(count), + }, + }, + }, + Action: sampleActionCreate, + Resources: []*authzV2.Resource{ + { + Resource: &authzV2.Resource_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + }, + } +} + func newDecisionMultiResourceRequestWithAttributeValueCount(count int) *authzV2.GetDecisionMultiResourceRequest { return &authzV2.GetDecisionMultiResourceRequest{ EntityIdentifier: newTokenEntityIdentifier(),
attribute_values Resource.AttributeValues

a set of attribute value FQNs, such as those on a TDF, between 1 and 20 in count

a set of attribute value FQNs, such as those on a TDF, containing at least 1 value