oso-cloud 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="