oso-cloud 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 062aa3d76c978722bbc9242d4a7c594eb0feb62146559dcc9a07b3d24f81f5c2
4
- data.tar.gz: 8f5afb67fd4e4242c35c7808df00b0bf0bbcd9fcd83257d3a34f218a33625070
3
+ metadata.gz: 7358c36375c04ca97c57ca8f326b49f1610b3a256aed07550a61a0f8208484aa
4
+ data.tar.gz: bf8823521cd89afe363c7a1241f7642c8badf49dc57a0a785330d129b963fe68
5
5
  SHA512:
6
- metadata.gz: bd87423b50e294bbf5ea4761a9287fd9a885b94a803ba8a51f56b428197b1bb37612828e7e58c8124e140ff6a0173d7f5614b6eb86141e6a2aeb4affa705ac83
7
- data.tar.gz: b3b1bfd172c4adcd344b2531875d1337cf0a34b3ccd12d0536a1c419a51c1a333ff2363f9d7ca9ab686f468de25bc0696ae2cae60bee2bfcc7ae59e83a956abc
6
+ metadata.gz: f7a1a6b6167562bb18fa9be85995a7e14384bc551d5ae0da3201f61aa8ece1c77ad7e1eaba9c47107feb2e7f8a92619cf92d386ce82525df601118f3744b47c2
7
+ data.tar.gz: '09d05422cfd5af07383c358d734e64a52a8508653c4719b6501c393c7ca52a85c68b26a973efbfca7a1ab5c172c1f01bd4418f8eec6906fabc0ee31bb2bde26a'
data/Gemfile.lock CHANGED
@@ -1,19 +1,21 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- oso-cloud (0.3.0)
4
+ oso-cloud (0.5.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
+ minitest (5.15.0)
9
10
  rake (12.3.3)
10
11
 
11
12
  PLATFORMS
12
13
  ruby
13
14
 
14
15
  DEPENDENCIES
16
+ minitest (~> 5.15)
15
17
  oso-cloud!
16
18
  rake (~> 12.0)
17
19
 
18
20
  BUNDLED WITH
19
- 2.1.4
21
+ 2.3.13
data/Rakefile CHANGED
@@ -1,2 +1,9 @@
1
- require "bundler/gem_tasks"
2
- task :default => :spec
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
data/lib/oso/client.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'json'
2
- require 'logger'
3
2
  require 'net/http'
4
3
  require 'uri'
5
4
 
@@ -7,73 +6,120 @@ require 'oso/version'
7
6
 
8
7
  module Oso
9
8
  class Client
10
- def initialize(url: 'https://cloud.osohq.com', api_key: nil, logger: nil)
9
+ def initialize(url: 'https://cloud.osohq.com', api_key: nil)
11
10
  @url = url
12
11
  @api_key = api_key
13
- @logger = logger || Logger.new(STDOUT)
14
- # TODO: why does this need to be configurable?
15
- # @to_type_and_id = method(default_to_type_and_id)
12
+ end
13
+
14
+ def policy(policy)
15
+ POST('policy', { src: policy })
16
16
  end
17
17
 
18
18
  def authorize(actor, action, resource)
19
- actor_type, actor_id = default_to_type_and_id actor
20
- resource_type, resource_id = default_to_type_and_id resource
21
- result = post('authorize', {
22
- actor_type: actor_type, actor_id: actor_id,
23
- action: action,
24
- resource_type: resource_type, resource_id: resource_id
25
- })
19
+ actor_typed_id = extract_typed_id actor
20
+ resource_typed_id = extract_typed_id resource
21
+ result = POST('authorize', {
22
+ actor_type: actor_typed_id.type, actor_id: actor_typed_id.id,
23
+ action: action,
24
+ resource_type: resource_typed_id.type, resource_id: resource_typed_id.id
25
+ })
26
26
  allowed = result['allowed']
27
- @logger.debug { "AUTHORIZING (#{actor}, #{action}, #{resource}) => ALLOWED? = #{allowed ? 'true' : 'false'} " }
28
-
29
27
  allowed
30
28
  end
31
29
 
30
+ def authorize_resources(actor, action, resources)
31
+ return [] if resources.nil?
32
+ return [] if resources.empty?
33
+
34
+ key = lambda do |type, id|
35
+ "#{type}:#{id}"
36
+ end
37
+
38
+ resources_extracted = resources.map { |r| extract_typed_id(r) }
39
+ actor_typed_id = extract_typed_id actor
40
+ result = POST('authorize_resources', {
41
+ actor_type: actor_typed_id.type, actor_id: actor_typed_id.id,
42
+ action: action,
43
+ resources: resources_extracted
44
+ })
45
+
46
+ return [] if result['results'].empty?
47
+
48
+ results_lookup = Hash.new
49
+ result['results'].each do |r|
50
+ k = key.call(r['type'], r['id'])
51
+ if results_lookup[k] == nil
52
+ results_lookup[k] = true
53
+ end
54
+ end
55
+
56
+ results = resources.select do |r|
57
+ e = extract_typed_id(r)
58
+ exists = results_lookup[key.call(e.type, e.id)]
59
+ exists
60
+ end
61
+ results
62
+ end
63
+
32
64
  def list(actor, action, resource_type)
33
- actor_type, actor_id = default_to_type_and_id actor
34
- result = post('list',
35
- {
36
- actor_type: actor_type, actor_id: actor_id,
37
- action: action,
38
- resource_type: resource_type
39
- })
65
+ actor_typed_id = extract_typed_id actor
66
+ result = POST('list', {
67
+ actor_type: actor_typed_id.type, actor_id: actor_typed_id.id,
68
+ action: action,
69
+ resource_type: resource_type,
70
+ })
71
+ results = result['results']
72
+ results
73
+ end
74
+
75
+ def actions(actor, resource)
76
+ actor_typed_id = extract_typed_id actor
77
+ resource_typed_id = extract_typed_id resource
78
+ result = POST('actions', {
79
+ actor_type: actor_typed_id.type, actor_id: actor_typed_id.id,
80
+ resource_type: resource_typed_id.type, resource_id: resource_typed_id.id,
81
+ })
40
82
  results = result['results']
41
- @logger.debug { "AUTHORIZING (#{actor}, #{action}, #{resource_type}). RESULTS: #{results}" }
42
-
43
83
  results
44
84
  end
45
85
 
46
- def add_role(actor, role_name, resource)
47
- add_role_or_relation('role', resource, role_name, actor)
86
+ def tell(predicate, *args)
87
+ typed_args = args.map { |a| extract_typed_id a}
88
+ POST('facts', { predicate: predicate, args: typed_args })
48
89
  end
49
90
 
50
- def delete_role(actor, role_name, resource)
51
- delete_role_or_relation('role', resource, role_name, actor)
91
+ def bulk_tell(facts)
92
+ params = facts.map { |predicate, *args|
93
+ typed_args = args.map { |a| extract_typed_id a}
94
+ { predicate: predicate, args: typed_args }
95
+ }
96
+ POST('bulk_load', params)
52
97
  end
53
98
 
54
- def add_relation(subject, name, object)
55
- add_role_or_relation('relation', subject, name, object)
99
+ def delete(predicate, *args)
100
+ typed_args = args.map { |a| extract_typed_id a}
101
+ DELETE('facts', { predicate: predicate, args: typed_args })
56
102
  end
57
103
 
58
- def delete_relation(subject, name, object)
59
- delete_role_or_relation('relation', subject, name, object)
104
+ def bulk_delete(facts)
105
+ params = facts.map { |predicate, *args|
106
+ typed_args = args.map { |a| extract_typed_id a}
107
+ { predicate: predicate, args: typed_args }
108
+ }
109
+ POST('bulk_delete', params)
60
110
  end
61
111
 
62
- def get_roles(resource: nil, role: nil, actor: nil)
63
- params = {}
64
- unless actor.nil?
65
- actor_type, actor_id = default_to_type_and_id actor
66
- params[:actor_type] = actor_type
67
- params[:actor_id] = actor_id
68
- end
69
- unless resource.nil?
70
- resource_type, resource_id = default_to_type_and_id resource
71
- params[:resource_type] = resource_type
72
- params[:resource_id] = resource_id
112
+ def get(predicate, *args)
113
+ params = {predicate: predicate}
114
+ args.each_with_index do |arg, i|
115
+ typed_id = extract_arg_query(arg)
116
+ if typed_id
117
+ params["args.#{i}.type"] = typed_id.type
118
+ params["args.#{i}.id"] = typed_id.id
119
+ end
73
120
  end
74
- params[:role] = role unless role.nil?
75
121
 
76
- get('roles')
122
+ GET('facts', params)
77
123
  end
78
124
 
79
125
  private
@@ -82,65 +128,68 @@ module Oso
82
128
  {
83
129
  "Authorization" => "Basic %s" % @api_key,
84
130
  "User-Agent" => "Oso Cloud (ruby)",
85
- "Accept": "application/json"
131
+ "Accept": "application/json",
132
+ "Content-Type": "application/json"
86
133
  }
87
134
  end
88
135
 
89
- def get(path)
90
- result = Net::HTTP.get(URI("#{@url}/api/#{path}"), headers)
136
+
137
+ def GET(path, params)
138
+ uri = URI("#{@url}/api/#{path}")
139
+ uri.query = URI::encode_www_form(params)
140
+ use_ssl = (uri.scheme == 'https')
141
+
142
+ result = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl ) { |http|
143
+ http.request(Net::HTTP::Get.new(uri, headers)) {|r|
144
+ r.read_body
145
+ }
146
+ }
91
147
  handle_result result
148
+
92
149
  end
93
150
 
94
- def post(path, params)
151
+ def POST(path, params)
95
152
  result = Net::HTTP.post(URI("#{@url}/api/#{path}"), params.to_json, headers)
96
153
  handle_result result
97
154
  end
98
155
 
99
- def delete(path, params)
100
- result = Net::HTTP.delete(URI("#{@url}/api/#{path}"), params.to_json, headers)
156
+ def DELETE(path, params)
157
+ uri = URI("#{@url}/api/#{path}")
158
+ use_ssl = (uri.scheme == 'https')
159
+ result = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl ) { |http|
160
+ http.request(Net::HTTP::Delete.new(uri, headers), params.to_json) {|r|
161
+ r.read_body
162
+ }
163
+ }
101
164
  handle_result result
102
165
  end
103
166
 
104
- # TODO: why does this need to be configurable?
105
- def default_to_type_and_id(obj)
106
- if obj.nil?
107
- %w[null null]
108
- else
109
- [obj.class.to_s, obj.id.to_s]
110
- end
111
- end
112
-
113
167
  def handle_result(result)
114
168
  unless result.is_a?(Net::HTTPSuccess)
115
169
  raise "Got an unexpected error from Oso Service: #{result.code}\n#{result.body}"
116
170
  end
117
171
 
118
- # TODO: Always JSON?
119
172
  JSON.parse(result.body)
120
173
  end
121
174
 
122
- def to_params(role_or_relation, from, name, to)
123
- from_type, from_id = default_to_type_and_id from
124
- to_type, to_id = default_to_type_and_id to
175
+ def extract_typed_id(x)
176
+ return TypedId.new(type: "String", id: x) if x.is_a? String
125
177
 
126
- from_name = role_or_relation == 'role' ? 'resource' : 'from'
127
- to_name = role_or_relation == 'role' ? 'actor' : 'to'
178
+ raise "#{x} does not have an 'id' field" unless x.respond_to? :id
179
+ raise "Invalid 'id' field on #{x}: #{x.id}" if x.id.nil?
128
180
 
129
- {
130
- "#{from_name}_id" => from_id,
131
- "#{from_name}_type" => from_type,
132
- role_or_relation.to_s => name,
133
- "#{to_name}_id" => to_id,
134
- "#{to_name}_type" => to_type
135
- }
181
+ TypedId.new(type: x.class.name, id: x.id.to_s)
136
182
  end
137
183
 
138
- def add_role_or_relation(role_or_relation, from, name, to)
139
- post("#{role_or_relation}s", to_params(role_or_relation, from, name, to))
184
+ def extract_arg_query(x)
185
+ return nil if x.nil?
186
+ extract_typed_id(x)
140
187
  end
141
188
 
142
- def delete_role_or_relation(role_or_relation, from, name, to)
143
- delete("#{role_or_relation}s", to_params(role_or_relation, from, name, to))
189
+ TypedId = Struct.new(:type, :id, keyword_init: true) do
190
+ def to_json(*args)
191
+ to_h.to_json(*args)
192
+ end
144
193
  end
145
194
  end
146
195
  end
data/lib/oso/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Oso
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '0.5.0'.freeze
3
3
  end
data/oso-cloud.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.summary = 'Oso authorization library.'
9
9
  spec.homepage = 'https://www.osohq.com/'
10
10
 
11
- spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
11
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
12
12
 
13
13
  # Specify which files should be added to the gem when it is released.
14
14
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -18,4 +18,6 @@ Gem::Specification.new do |spec|
18
18
  spec.bindir = 'exe'
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ['lib']
21
+
22
+ spec.add_development_dependency 'minitest', '~> 5.15'
21
23
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oso-cloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oso Security, Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-05-13 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-05-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.15'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.15'
13
27
  description:
14
28
  email:
15
29
  - support@osohq.com
@@ -38,7 +52,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
38
52
  requirements:
39
53
  - - ">="
40
54
  - !ruby/object:Gem::Version
41
- version: 2.3.0
55
+ version: 2.7.0
42
56
  required_rubygems_version: !ruby/object:Gem::Requirement
43
57
  requirements:
44
58
  - - ">="