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 +4 -4
- data/Gemfile.lock +4 -2
- data/Rakefile +9 -2
- data/lib/oso/client.rb +125 -76
- data/lib/oso/version.rb +1 -1
- data/oso-cloud.gemspec +3 -1
- metadata +18 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7358c36375c04ca97c57ca8f326b49f1610b3a256aed07550a61a0f8208484aa
|
4
|
+
data.tar.gz: bf8823521cd89afe363c7a1241f7642c8badf49dc57a0a785330d129b963fe68
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
21
|
+
2.3.13
|
data/Rakefile
CHANGED
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
|
9
|
+
def initialize(url: 'https://cloud.osohq.com', api_key: nil)
|
11
10
|
@url = url
|
12
11
|
@api_key = api_key
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
20
|
-
|
21
|
-
result =
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
34
|
-
result =
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
47
|
-
|
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
|
51
|
-
|
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
|
55
|
-
|
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
|
59
|
-
|
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
|
63
|
-
params = {}
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
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
|
-
|
90
|
-
|
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
|
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
|
100
|
-
|
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
|
123
|
-
|
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
|
-
|
127
|
-
|
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
|
139
|
-
|
184
|
+
def extract_arg_query(x)
|
185
|
+
return nil if x.nil?
|
186
|
+
extract_typed_id(x)
|
140
187
|
end
|
141
188
|
|
142
|
-
|
143
|
-
|
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
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.
|
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.
|
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-
|
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.
|
55
|
+
version: 2.7.0
|
42
56
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
57
|
requirements:
|
44
58
|
- - ">="
|