Skip to content

Commit

Permalink
enhancement: Support schema validation in `Cerbos::Client#plan_resour…
Browse files Browse the repository at this point in the history
…ces`

Signed-off-by: Andrew Haines <haines@cerbos.dev>
  • Loading branch information
haines committed Jun 17, 2022
1 parent f5393c8 commit 9c15fe3
Show file tree
Hide file tree
Showing 18 changed files with 318 additions and 67 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
@@ -1,5 +1,12 @@
## [Unreleased]
No notable changes.
### Added
- Support for schema validation in `Cerbos::Client#plan_resources` ([#32](https://github.com/cerbos/cerbos-sdk-ruby/pull/32))

Requires Cerbos 0.19+.
`Cerbos::Output::PlanResources#validation_errors` will always return an empty array if the client is connected to an earlier version of Cerbos.

As a result, `Cerbos::Output::CheckResources::Result::ValidationError` has moved to `Cerbos::Output::ValidationError`.
Attempting to access the class via the old namespace will print a deprecation warning and return the new class.

## [0.5.0] - 2022-06-09
### Added
Expand Down
6 changes: 4 additions & 2 deletions lib/cerbos/client.rb
Expand Up @@ -187,7 +187,9 @@ def plan_resources(principal:, resource:, action:, aux_data: nil, include_metada

response = perform_request(@cerbos_service, :plan_resources, request)

Output::PlanResources.from_protobuf(response)
Output::PlanResources.from_protobuf(response).tap do |output|
handle_validation_errors output
end
end
end

Expand Down Expand Up @@ -221,7 +223,7 @@ def handle_errors
def handle_validation_errors(output)
return if @on_validation_error == :return

validation_errors = output.results.flat_map(&:validation_errors)
validation_errors = output.validation_errors
return if validation_errors.empty?

raise Error::ValidationFailed.new(validation_errors) if @on_validation_error == :raise
Expand Down
1 change: 1 addition & 0 deletions lib/cerbos/output.rb
Expand Up @@ -32,6 +32,7 @@ def hash
end
end

require_relative "output/validation_error"
require_relative "output/check_resources"
require_relative "output/plan_resources"
require_relative "output/server_info"
59 changes: 18 additions & 41 deletions lib/cerbos/output/check_resources.rb
Expand Up @@ -55,6 +55,13 @@ def find_result(resource)
results.find { |result| matching_resource?(search, result.resource) }
end

# List unique schema validation errors for the principal or resource attributes.
#
# @return [Array<ValidationError>]
def validation_errors
results.flat_map(&:validation_errors).uniq
end

private

def matching_resource?(search, candidate)
Expand Down Expand Up @@ -88,11 +95,21 @@ def matching_resource?(search, candidate)
# @return [Metadata]
# @return [nil] if `include_metadata` was `false`.

# @private
def self.const_missing(const)
if const == :ValidationError
warn "#{name}::ValidationError is deprecated; use #{ValidationError.name} instead (called from #{caller(1..1).first})"
return ValidationError
end

super
end

def self.from_protobuf(entry)
new(
resource: CheckResources::Result::Resource.from_protobuf(entry.resource),
actions: entry.actions.to_h,
validation_errors: (entry.validation_errors || []).map { |validation_error| CheckResources::Result::ValidationError.from_protobuf(validation_error) },
validation_errors: (entry.validation_errors || []).map { |validation_error| ValidationError.from_protobuf(validation_error) },
metadata: CheckResources::Result::Metadata.from_protobuf(entry.meta)
)
end
Expand Down Expand Up @@ -154,46 +171,6 @@ def self.from_protobuf(resource)
end
end

# An error that occurred while validating the principal or resource attributes against a schema.
CheckResources::Result::ValidationError = Output.new_class(:path, :message, :source) do
# @!attribute [r] path
# The path to the attribute that failed validation.
#
# @return [String]

# @!attribute [r] message
# The error message.
#
# @return [String]

# @!attribute [r] source
# The source of the invalid attributes.
#
# @return [:SOURCE_PRINCIPAL, :SOURCE_RESOURCE]

def self.from_protobuf(validation_error)
new(
path: validation_error.path,
message: validation_error.message,
source: validation_error.source
)
end

# Check if the principal's attributes failed schema validation.
#
# @return [Boolean]
def from_principal?
source == :SOURCE_PRINCIPAL
end

# Check if the resource's attributes failed schema validation.
#
# @return [Boolean]
def from_resource?
source == :SOURCE_RESOURCE
end
end

# Additional information about how policy decisions were reached.
CheckResources::Result::Metadata = Output.new_class(:actions, :effective_derived_roles) do
# @!attribute [r] actions
Expand Down
8 changes: 7 additions & 1 deletion lib/cerbos/output/plan_resources.rb
Expand Up @@ -5,7 +5,7 @@ module Output
# A query plan that can be used to obtain a list of resources on which a principal is allowed to perform a particular action.
#
# @see Client#plan_resources
PlanResources = Output.new_class(:request_id, :kind, :condition, :metadata) do
PlanResources = Output.new_class(:request_id, :kind, :condition, :validation_errors, :metadata) do
# @!attribute [r] request_id
# The identifier for tracing the request.
#
Expand All @@ -26,6 +26,11 @@ module Output
# @see #always_denied?
# @see #conditional?

# @!attribute [r] validation_errors
# Any schema validation errors for the principal or resource attributes.
#
# @return [Array<ValidationError>]

# @!attribute [r] metadata
# Additional information about the query plan.
#
Expand All @@ -37,6 +42,7 @@ def self.from_protobuf(plan_resources)
request_id: plan_resources.request_id,
kind: plan_resources.filter.kind,
condition: PlanResources::Expression::Operand.from_protobuf(plan_resources.filter.condition),
validation_errors: (plan_resources.validation_errors || []).map { |validation_error| ValidationError.from_protobuf(validation_error) },
metadata: PlanResources::Metadata.from_protobuf(plan_resources.meta)
)
end
Expand Down
45 changes: 45 additions & 0 deletions lib/cerbos/output/validation_error.rb
@@ -0,0 +1,45 @@
# frozen_string_literal: true

module Cerbos
module Output
# An error that occurred while validating the principal or resource attributes against a schema.
ValidationError = Output.new_class(:path, :message, :source) do
# @!attribute [r] path
# The path to the attribute that failed validation.
#
# @return [String]

# @!attribute [r] message
# The error message.
#
# @return [String]

# @!attribute [r] source
# The source of the invalid attributes.
#
# @return [:SOURCE_PRINCIPAL, :SOURCE_RESOURCE]

def self.from_protobuf(validation_error)
new(
path: validation_error.path,
message: validation_error.message,
source: validation_error.source
)
end

# Check if the principal's attributes failed schema validation.
#
# @return [Boolean]
def from_principal?
source == :SOURCE_PRINCIPAL
end

# Check if the resource's attributes failed schema validation.
#
# @return [Boolean]
def from_resource?
source == :SOURCE_RESOURCE
end
end
end
end
1 change: 1 addition & 0 deletions lib/cerbos/protobuf/cerbos/engine/v1/engine_pb.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/cerbos/protobuf/cerbos/response/v1/response_pb.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 9c15fe3

Please sign in to comment.