require 'json' require 'net/http' require 'uri' require 'oso/version' module Oso class Client def initialize(url: 'https://cloud.osohq.com', api_key: nil) @url = url @api_key = api_key end def policy(policy) POST('policy', { src: policy }) end def authorize(actor, action, resource) actor_typed_id = extract_typed_id actor resource_typed_id = extract_typed_id resource result = POST('authorize', { 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 }) allowed = result['allowed'] allowed end def authorize_resources(actor, action, resources) return [] if resources.nil? return [] if resources.empty? key = lambda do |type, id| "#{type}:#{id}" end resources_extracted = resources.map { |r| extract_typed_id(r) } actor_typed_id = extract_typed_id actor result = POST('authorize_resources', { actor_type: actor_typed_id.type, actor_id: actor_typed_id.id, action: action, resources: resources_extracted }) return [] if result['results'].empty? results_lookup = Hash.new result['results'].each do |r| k = key.call(r['type'], r['id']) if results_lookup[k] == nil results_lookup[k] = true end end results = resources.select do |r| e = extract_typed_id(r) exists = results_lookup[key.call(e.type, e.id)] exists end results end def list(actor, action, resource_type) actor_typed_id = extract_typed_id actor result = POST('list', { actor_type: actor_typed_id.type, actor_id: actor_typed_id.id, action: action, resource_type: resource_type, }) results = result['results'] results end def actions(actor, resource) actor_typed_id = extract_typed_id actor resource_typed_id = extract_typed_id resource result = POST('actions', { actor_type: actor_typed_id.type, actor_id: actor_typed_id.id, resource_type: resource_typed_id.type, resource_id: resource_typed_id.id, }) results = result['results'] results end def tell(predicate, *args) typed_args = args.map { |a| extract_typed_id a} POST('facts', { predicate: predicate, args: typed_args }) end def bulk_tell(facts) params = facts.map { |predicate, *args| typed_args = args.map { |a| extract_typed_id a} { predicate: predicate, args: typed_args } } POST('bulk_load', params) end def delete(predicate, *args) typed_args = args.map { |a| extract_typed_id a} DELETE('facts', { predicate: predicate, args: typed_args }) end def bulk_delete(facts) params = facts.map { |predicate, *args| typed_args = args.map { |a| extract_typed_id a} { predicate: predicate, args: typed_args } } POST('bulk_delete', params) end def get(predicate, *args) params = {predicate: predicate} args.each_with_index do |arg, i| typed_id = extract_arg_query(arg) if typed_id params["args.#{i}.type"] = typed_id.type params["args.#{i}.id"] = typed_id.id end end GET('facts', params) end private def headers() { "Authorization" => "Basic %s" % @api_key, "User-Agent" => "Oso Cloud (ruby)", "Accept": "application/json", "Content-Type": "application/json" } end def GET(path, params) uri = URI("#{@url}/api/#{path}") uri.query = URI::encode_www_form(params) use_ssl = (uri.scheme == 'https') result = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl ) { |http| http.request(Net::HTTP::Get.new(uri, headers)) {|r| r.read_body } } handle_result result end def POST(path, params) result = Net::HTTP.post(URI("#{@url}/api/#{path}"), params.to_json, headers) handle_result result end def DELETE(path, params) uri = URI("#{@url}/api/#{path}") use_ssl = (uri.scheme == 'https') result = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl ) { |http| http.request(Net::HTTP::Delete.new(uri, headers), params.to_json) {|r| r.read_body } } handle_result result end def handle_result(result) unless result.is_a?(Net::HTTPSuccess) raise "Got an unexpected error from Oso Service: #{result.code}\n#{result.body}" end JSON.parse(result.body) end def extract_typed_id(x) return TypedId.new(type: "String", id: x) if x.is_a? String raise "#{x} does not have an 'id' field" unless x.respond_to? :id raise "Invalid 'id' field on #{x}: #{x.id}" if x.id.nil? TypedId.new(type: x.class.name, id: x.id.to_s) end def extract_arg_query(x) return nil if x.nil? extract_typed_id(x) end TypedId = Struct.new(:type, :id, keyword_init: true) do def to_json(*args) to_h.to_json(*args) end end end end