require 'json' require 'net/http' require 'uri' require 'oso/version' require 'oso/api' require 'oso/helpers' ## # For more detailed documentation, see # https://www.osohq.com/docs/reference/client-apis/ruby module OsoCloud # Represents an object in your application, with a type and id. # Both "type" and "id" should be strings. Value = Struct.new(:type, :id, keyword_init: true) do def to_api_value OsoCloud::Helpers.extract_value(self) end end # Oso Cloud client for Ruby # # About facts: # # Some of these methods accept and return "fact"s. # A "fact" is an array with at least one element. # The first element must be a string, representing the fact's name. # Any other elements in the array, which together represent the fact's arguments, # can be "OsoCloud::Value" objects or strings. class Oso attr_reader :experimental def initialize(url: 'https://cloud.osohq.com', api_key: nil, fallback_url: nil, data_bindings: nil) @api = OsoCloud::Core::Api.new(url: url, api_key: api_key, data_bindings: data_bindings, options: { :fallback_url => fallback_url }) @experimental = Experimental.new(@api) end class Experimental def initialize(api) @api = api end ## # Check a permission depending on data both in Oso Cloud and stored in a local database # # Returns a SQL query to run against the local database # # @param actor [OsoCloud::Value] # @param action [String] # @param resource [OsoCloud::Value] # @param column [String] # @return [Array] def authorize_local(actor, action, resource) actor_typed_id = actor.to_api_value resource_typed_id = resource.to_api_value result = @api.post_authorize_query( OsoCloud::Core::AuthorizeQuery.new( actor_type: actor_typed_id.type, actor_id: actor_typed_id.id, action: action, resource_type: resource_typed_id.type, resource_id: resource_typed_id.id, context_facts: [] ) ) result.sql end ## # List authorized resources depending on data both in Oso Cloud and stored in a local database # # Returns a SQL query to run against the local database # # @param actor [OsoCloud::Value] # @param action [String] # @param resource_type [String] # @return [Array] def list_local(actor, action, resource_type, column) actor_typed_id = actor.to_api_value result = @api.post_list_query( query: OsoCloud::Core::ListQuery.new( actor_type: actor_typed_id.type, actor_id: actor_typed_id.id, action: action, resource_type: resource_type, context_facts: [] ), column: column ) result.sql end end ## # Update the active policy # # Updates the active policy in Oso Cloud, The string passed into # this method should be written in Polar. # # @param policy [String] # @return [nil] def policy(policy) @api.post_policy(OsoCloud::Core::Policy.new(src: policy, filename: '')) nil end ## # Returns metadata about the currently active policy def get_policy_metadata @api.get_policy_metadata.metadata end ## # Check a permission # # Returns true if the actor can perform the action on the resource; # otherwise false. # # @param actor [OsoCloud::Value] # @param action [String] # @param resource [OsoCloud::Value] # @param context_facts [Array] # @return [Boolean] # @see Oso for more information about facts def authorize(actor, action, resource, context_facts = []) actor_typed_id = actor.to_api_value resource_typed_id = resource.to_api_value result = @api.post_authorize(OsoCloud::Core::AuthorizeQuery.new( actor_type: actor_typed_id.type, actor_id: actor_typed_id.id, action: action, resource_type: resource_typed_id.type, resource_id: resource_typed_id.id, context_facts: OsoCloud::Helpers.params_to_facts(context_facts) )) result.allowed end ## # Check authorized resources # # Returns a subset of the resource which an actor can perform # a particular action. Ordering and duplicates, if any exist, are preserved. # # @param actor [OsoCloud::Value] # @param action [String] # @param resources [Array] # @param context_facts [Array] # @return [Array] # @see Oso for more information about facts def authorize_resources(actor, action, resources, context_facts = []) return [] if resources.nil? return [] if resources.empty? key = lambda do |type, id| "#{type}:#{id}" end resources_extracted = resources.map(&:to_api_value) actor_typed_id = actor.to_api_value data = OsoCloud::Core::AuthorizeResourcesQuery.new( actor_type: actor_typed_id.type, actor_id: actor_typed_id.id, action: action, resources: resources_extracted, context_facts: OsoCloud::Helpers.params_to_facts(context_facts) ) result = @api.post_authorize_resources(data) return [] if result.results.empty? results_lookup = {} result.results.each do |r| k = key.call(r.type, r.id) results_lookup[k] = true if results_lookup[k].nil? end resources.select do |r| e = r.to_api_value exists = results_lookup[key.call(e.type, e.id)] exists end end ## # List authorized resources # # Fetches a list of resource ids on which an actor can perform a # particular action. # # @param actor [OsoCloud::Value] # @param action [String] # @param resource_type [String] # @param context_facts [Array] # @return [Array] # @see Oso for more information about facts def list(actor, action, resource_type, context_facts = []) actor_typed_id = actor.to_api_value result = @api.post_list(OsoCloud::Core::ListQuery.new( actor_type: actor_typed_id.type, actor_id: actor_typed_id.id, action: action, resource_type: resource_type, context_facts: OsoCloud::Helpers.params_to_facts(context_facts) )) result.results end ## # List authorized actions # # Fetches a list of actions which an actor can perform on a particular resource. # # @param actor [OsoCloud::Value] # @param resource [OsoCloud::Value] # @param context_facts [Array] # @return [Array] # @see Oso for more information about facts def actions(actor, resource, context_facts = []) actor_typed_id = actor.to_api_value resource_typed_id = resource.to_api_value result = @api.post_actions(OsoCloud::Core::ActionsQuery.new( actor_type: actor_typed_id.type, actor_id: actor_typed_id.id, resource_type: resource_typed_id.type, resource_id: resource_typed_id.id, context_facts: OsoCloud::Helpers.params_to_facts(context_facts) )) result.results end ## # Add a fact # # Adds a fact with the given name and arguments. # # @param name [String] # @param args [*[String, OsoCloud::Value]] # @return [nil] def tell(name, *args) typed_args = args.map { |a| OsoCloud::Helpers.extract_value(a) } @api.post_facts(OsoCloud::Core::Fact.new(predicate: name, args: typed_args)) nil end ## # Add many facts # # Adds many facts at once. # # @param facts [Array] # @return [nil] # @see Oso for more information about facts def bulk_tell(facts) @api.post_bulk_load(OsoCloud::Helpers.params_to_facts(facts)) nil end ## # Delete fact # # Deletes a fact. Does not throw an error if the fact is not found. # # @param name [String] # @param args [*[String, OsoCloud::Value]] # @return [nil] def delete(name, *args) typed_args = args.map { |a| OsoCloud::Helpers.extract_value(a) } @api.delete_facts(OsoCloud::Core::Fact.new(predicate: name, args: typed_args)) nil end ## # Delete many facts # # Deletes many facts at once. Does not throw an error when some of # the facts are not found. # # @param facts [Array] # @return [nil] # @see Oso for more information about facts def bulk_delete(facts) @api.post_bulk_delete(OsoCloud::Helpers.params_to_facts(facts)) nil end ## # Transactionally delete and insert fact(s) # # Delete(s) are processed before insertion(s). nil arguments in facts to be # deleted act as wildcards. Does not throw an error if facts to be deleted # are not found or facts to be inserted already exist. # # # Throws an OsoCloud::Core::Api exception if error returned from server. # # @param delete [Array] # @param insert [Array] # @return [nil] # @see Oso for more information about facts def bulk(delete: [], insert: []) @api.post_bulk(OsoCloud::Core::Bulk.new(delete: OsoCloud::Helpers.params_to_facts(delete), tell: OsoCloud::Helpers.params_to_facts(insert))) nil end ## # List facts # # Lists facts that are stored in Oso Cloud. Can be used to check the existence # of a particular fact, or used to fetch all facts that have a particular # argument. nil arguments operate as wildcards. # # @param name [String] # @param args [*[String, OsoCloud::Value, nil]] # @return [Array] # @see Oso for more information about facts def get(name, *args) OsoCloud::Helpers.facts_to_params(@api.get_facts(name, args)) end ## # List added and derived facts # # Lists facts that are stored in Oso Cloud in addition to derived facts # from evaluating the policy. nil arguments operate as wildcards. # # @param name [String] # @param args [Array<[String, OsoCloud::Value, nil]>] # @param context_facts [Array] # @return [Array] # @see Oso for more information about facts def query(name, *args, context_facts: []) typed_args = args.map { |a| OsoCloud::Helpers.extract_value(a) } result = @api.post_query(OsoCloud::Core::Query.new(fact: OsoCloud::Helpers.param_to_fact(name, typed_args), context_facts: OsoCloud::Helpers.params_to_facts(context_facts))) OsoCloud::Helpers.facts_to_params(result.results) end ## # List authorized actions for a batch of queries # # Fetches a list of actions which an actor can perform on a particular resource. # # @param actor [OsoCloud::Value] # @param queries [Array] | Array<[OsoCloud::Value, Array]> # @return [Array>] # @see Oso for more information about facts def bulk_actions(actor, queries:) actor_typed_id = actor.to_api_value data = queries.map do |q| context_facts = [] resource = nil if (q.is_a?(Array)) resource = q[0] context_facts = q[1] else resource = q end resource_typed_id = resource.to_api_value OsoCloud::Core::ActionsQuery.new( actor_type: actor_typed_id.type, actor_id: actor_typed_id.id, resource_type: resource_typed_id.type, resource_id: resource_typed_id.id, context_facts: OsoCloud::Helpers.params_to_facts(context_facts) ) end @api.post_bulk_actions(data).map { |result| result.results} end end end