oso-cloud 0.7.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +10 -1
- data/README.md +42 -14
- data/lib/oso/api.rb +458 -0
- data/lib/oso/helpers.rb +59 -0
- data/lib/oso/oso.rb +255 -0
- data/lib/oso/version.rb +2 -2
- data/lib/oso-cloud.rb +1 -0
- data/oso-cloud.gemspec +6 -3
- metadata +39 -7
- data/lib/oso/client.rb +0 -198
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 339079e696596a482f6f1fe6bc875d9f88dc5415b0eaca4b4e7a04304280cc7a
|
4
|
+
data.tar.gz: '099c6b0532405a7fc0cf5933b09ea03991fe43f3e2da6b726d5e33e932706b55'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 441e11c7fdb4b201cf22d84195d7f0cc5454a64186c0078086ae085138a31a8ee10f667f5723b94b467aa74ee89780e1ccd853c435d044f84616b46f78a44527
|
7
|
+
data.tar.gz: 56c4cb7d88820805bbd9238624f8220253c48aa19fdffe71298af26ff9369e3bf5692d49be70f439a582e4f76ff9d4b7458f45a303fa32c02296c5c2324f9f08
|
data/Gemfile.lock
CHANGED
@@ -1,13 +1,22 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
oso-cloud (0.
|
4
|
+
oso-cloud (1.0.1)
|
5
|
+
faraday (~> 2.5.2)
|
6
|
+
faraday-retry (~> 2.0.0)
|
5
7
|
|
6
8
|
GEM
|
7
9
|
remote: https://rubygems.org/
|
8
10
|
specs:
|
11
|
+
faraday (2.5.2)
|
12
|
+
faraday-net_http (>= 2.0, < 3.1)
|
13
|
+
ruby2_keywords (>= 0.0.4)
|
14
|
+
faraday-net_http (3.0.2)
|
15
|
+
faraday-retry (2.0.0)
|
16
|
+
faraday (~> 2.0)
|
9
17
|
minitest (5.15.0)
|
10
18
|
rake (12.3.3)
|
19
|
+
ruby2_keywords (0.0.5)
|
11
20
|
|
12
21
|
PLATFORMS
|
13
22
|
ruby
|
data/README.md
CHANGED
@@ -1,25 +1,53 @@
|
|
1
|
-
# Oso
|
1
|
+
# Oso Cloud Client for Ruby
|
2
2
|
|
3
|
-
|
3
|
+
[![Slack][badge-slack]][badge-slack-link]
|
4
4
|
|
5
|
-
|
5
|
+
The Oso Cloud client for Ruby provides a convenient wrapper around the Oso
|
6
|
+
Cloud HTTP API for applications and services written in Ruby.
|
6
7
|
|
7
|
-
##
|
8
|
+
## What is Oso Cloud?
|
9
|
+
Oso Cloud is authorization-as-a-service. It provides abstractions for building
|
10
|
+
and iterating on authorization in your application – based on years of work
|
11
|
+
with hundreds of engineering teams.
|
8
12
|
|
9
|
-
|
13
|
+
- Model: Build your authorization model using primitives for common patterns
|
14
|
+
like multi-tenancy and RBAC. Express custom rules using Polar, a
|
15
|
+
declarative policy language for authorization.
|
10
16
|
|
11
|
-
|
12
|
-
|
13
|
-
```
|
17
|
+
- Store: Store your authorization data using a best-practices data model and
|
18
|
+
use it for access decisions across all of your services.
|
14
19
|
|
15
|
-
|
20
|
+
- Enforce & Query: Add enforcement calls to your application to perform
|
21
|
+
yes/no permission checks, filter resources by permissions, list a user's
|
22
|
+
roles, and show/hide pieces of your UI.
|
16
23
|
|
17
|
-
|
24
|
+
- Test & Watch: Write tests over your authorization policies before you push
|
25
|
+
them live. See logs of authorization decisions in real time.
|
18
26
|
|
19
|
-
|
27
|
+
For more information on how Oso Cloud works and how it fits into your
|
28
|
+
architecture, check out the
|
29
|
+
[introduction](https://www.osohq.com/docs/get-started/what-is-oso-cloud).
|
20
30
|
|
21
|
-
|
31
|
+
## Documentation
|
32
|
+
- To get up and running with Oso Cloud, try the
|
33
|
+
[Quickstart guide](https://www.osohq.com/docs/get-started/quickstart).
|
34
|
+
- For method-level documentation, see the
|
35
|
+
[Ruby Client API documentation](https://www.osohq.com/docs/reference/client-apis/ruby).
|
36
|
+
- Full documentation is available at
|
37
|
+
[osohq.com/docs](https://www.osohq.com/docs).
|
38
|
+
- To learn about authorization best practices (not specific to Oso), read the
|
39
|
+
[Authorization Academy](https://www.osohq.com/developers/authorization-academy)
|
40
|
+
guides.
|
22
41
|
|
23
|
-
##
|
42
|
+
## Community & Support
|
43
|
+
|
44
|
+
If you have any questions on Oso Cloud or authorization more generally, you can
|
45
|
+
join our engineering team & hundreds of other developers using Oso in our
|
46
|
+
community Slack:
|
47
|
+
|
48
|
+
[![Button][join-slack-link]][badge-slack-link]
|
49
|
+
|
50
|
+
[join-slack-link]: https://user-images.githubusercontent.com/282595/128394344-1bd9e5b2-e83d-4666-b446-2e4f431ffcea.png
|
51
|
+
[badge-slack]: https://img.shields.io/badge/slack-oso--oss-orange
|
52
|
+
[badge-slack-link]: https://join-slack.osohq.com/
|
24
53
|
|
25
|
-
TODO: Write usage instructions here
|
data/lib/oso/api.rb
ADDED
@@ -0,0 +1,458 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'uri'
|
3
|
+
require 'faraday'
|
4
|
+
require 'faraday/retry'
|
5
|
+
|
6
|
+
require 'oso/helpers'
|
7
|
+
|
8
|
+
module OsoCloud
|
9
|
+
# @!visibility private
|
10
|
+
module Core
|
11
|
+
# @!visibility private
|
12
|
+
class ApiResult
|
13
|
+
attr_reader :message
|
14
|
+
|
15
|
+
def initialize(message:)
|
16
|
+
@message = message
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @!visibility private
|
21
|
+
class ApiError < StandardError
|
22
|
+
def initialize(message:)
|
23
|
+
super(message)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @!visibility private
|
28
|
+
class Policy
|
29
|
+
attr_reader :filename
|
30
|
+
attr_reader :src
|
31
|
+
|
32
|
+
def initialize(filename:, src:)
|
33
|
+
@filename = filename
|
34
|
+
@src = src
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @!visibility private
|
39
|
+
class GetPolicyResult
|
40
|
+
attr_reader :policy
|
41
|
+
|
42
|
+
def initialize(policy:)
|
43
|
+
if policy.is_a? Policy
|
44
|
+
@policy = policy
|
45
|
+
else
|
46
|
+
@policy = Policy.new(**policy)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @!visibility private
|
52
|
+
class Fact
|
53
|
+
attr_reader :predicate
|
54
|
+
attr_reader :args
|
55
|
+
|
56
|
+
def initialize(predicate:, args:)
|
57
|
+
@predicate = predicate
|
58
|
+
@args = args.map { |v| if v.is_a? Value then v else Value.new(**v) end }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @!visibility private
|
63
|
+
class Value
|
64
|
+
attr_reader :type
|
65
|
+
attr_reader :id
|
66
|
+
|
67
|
+
def initialize(type:, id:)
|
68
|
+
@type = type
|
69
|
+
@id = id
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# @!visibility private
|
74
|
+
class Bulk
|
75
|
+
attr_reader :delete
|
76
|
+
attr_reader :tell
|
77
|
+
|
78
|
+
def initialize(delete:, tell:)
|
79
|
+
@delete = delete.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
80
|
+
@tell = tell.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# @!visibility private
|
85
|
+
class AuthorizeResult
|
86
|
+
attr_reader :allowed
|
87
|
+
|
88
|
+
def initialize(allowed:)
|
89
|
+
@allowed = allowed
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# @!visibility private
|
94
|
+
class AuthorizeQuery
|
95
|
+
attr_reader :actor_type
|
96
|
+
attr_reader :actor_id
|
97
|
+
attr_reader :action
|
98
|
+
attr_reader :resource_type
|
99
|
+
attr_reader :resource_id
|
100
|
+
attr_reader :context_facts
|
101
|
+
|
102
|
+
def initialize(actor_type:, actor_id:, action:, resource_type:, resource_id:, context_facts:)
|
103
|
+
@actor_type = actor_type
|
104
|
+
@actor_id = actor_id
|
105
|
+
@action = action
|
106
|
+
@resource_type = resource_type
|
107
|
+
@resource_id = resource_id
|
108
|
+
@context_facts = context_facts.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# @!visibility private
|
113
|
+
class AuthorizeResourcesResult
|
114
|
+
attr_reader :results
|
115
|
+
|
116
|
+
def initialize(results:)
|
117
|
+
@results = results.map { |v| if v.is_a? Value then v else Value.new(**v) end }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# @!visibility private
|
122
|
+
class AuthorizeResourcesQuery
|
123
|
+
attr_reader :actor_type
|
124
|
+
attr_reader :actor_id
|
125
|
+
attr_reader :action
|
126
|
+
attr_reader :resources
|
127
|
+
attr_reader :context_facts
|
128
|
+
|
129
|
+
def initialize(actor_type:, actor_id:, action:, resources:, context_facts:)
|
130
|
+
@actor_type = actor_type
|
131
|
+
@actor_id = actor_id
|
132
|
+
@action = action
|
133
|
+
@resources = resources.map { |v| if v.is_a? Value then v else Value.new(**v) end }
|
134
|
+
@context_facts = context_facts.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# @!visibility private
|
139
|
+
class ListResult
|
140
|
+
attr_reader :results
|
141
|
+
|
142
|
+
def initialize(results:)
|
143
|
+
@results = results
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# @!visibility private
|
148
|
+
class ListQuery
|
149
|
+
attr_reader :actor_type
|
150
|
+
attr_reader :actor_id
|
151
|
+
attr_reader :action
|
152
|
+
attr_reader :resource_type
|
153
|
+
attr_reader :context_facts
|
154
|
+
|
155
|
+
def initialize(actor_type:, actor_id:, action:, resource_type:, context_facts:)
|
156
|
+
@actor_type = actor_type
|
157
|
+
@actor_id = actor_id
|
158
|
+
@action = action
|
159
|
+
@resource_type = resource_type
|
160
|
+
@context_facts = context_facts.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# @!visibility private
|
165
|
+
class ActionsResult
|
166
|
+
attr_reader :results
|
167
|
+
|
168
|
+
def initialize(results:)
|
169
|
+
@results = results
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# @!visibility private
|
174
|
+
class ActionsQuery
|
175
|
+
attr_reader :actor_type
|
176
|
+
attr_reader :actor_id
|
177
|
+
attr_reader :resource_type
|
178
|
+
attr_reader :resource_id
|
179
|
+
attr_reader :context_facts
|
180
|
+
|
181
|
+
def initialize(actor_type:, actor_id:, resource_type:, resource_id:, context_facts:)
|
182
|
+
@actor_type = actor_type
|
183
|
+
@actor_id = actor_id
|
184
|
+
@resource_type = resource_type
|
185
|
+
@resource_id = resource_id
|
186
|
+
@context_facts = context_facts.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# @!visibility private
|
191
|
+
class QueryResult
|
192
|
+
attr_reader :results
|
193
|
+
|
194
|
+
def initialize(results:)
|
195
|
+
@results = results.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# @!visibility private
|
200
|
+
class Query
|
201
|
+
attr_reader :fact
|
202
|
+
attr_reader :context_facts
|
203
|
+
|
204
|
+
def initialize(fact:, context_facts:)
|
205
|
+
if fact.is_a? Fact
|
206
|
+
@fact = fact
|
207
|
+
else
|
208
|
+
@fact = Fact.new(**fact)
|
209
|
+
end
|
210
|
+
@context_facts = context_facts.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# @!visibility private
|
215
|
+
class StatsResult
|
216
|
+
attr_reader :num_roles
|
217
|
+
attr_reader :num_relations
|
218
|
+
attr_reader :num_facts
|
219
|
+
|
220
|
+
def initialize(num_roles:, num_relations:, num_facts:)
|
221
|
+
@num_roles = num_roles
|
222
|
+
@num_relations = num_relations
|
223
|
+
@num_facts = num_facts
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
# @!visibility private
|
229
|
+
class Api
|
230
|
+
def initialize(url: 'https://cloud.osohq.com', api_key: nil, options: nil)
|
231
|
+
@url = url
|
232
|
+
@connection = Faraday.new(url: url) do |faraday|
|
233
|
+
faraday.request :json
|
234
|
+
|
235
|
+
# responses are processed in reverse order; this stack implies the
|
236
|
+
# retries are attempted before an error is raised, and the json
|
237
|
+
# parser is only applied if there are no errors
|
238
|
+
faraday.response :json, preserve_raw: true
|
239
|
+
faraday.response :raise_error
|
240
|
+
faraday.request :retry, {
|
241
|
+
max: (options && options[:max_retries]) || 10,
|
242
|
+
interval: 0.01,
|
243
|
+
interval_randomness: 0.005,
|
244
|
+
max_interval: 1,
|
245
|
+
backoff_factor: 2,
|
246
|
+
retry_statuses: [429, 500, 502, 503, 504],
|
247
|
+
# ensure authorize and related check functions are retried because
|
248
|
+
# they are POST requests, which are not retried automatically
|
249
|
+
retry_if: ->(env, _exc) {
|
250
|
+
%w[
|
251
|
+
/api/authorize
|
252
|
+
/api/authorize_resources
|
253
|
+
/api/list
|
254
|
+
/api/actions
|
255
|
+
/api/query
|
256
|
+
].include? env.url.path
|
257
|
+
},
|
258
|
+
}
|
259
|
+
|
260
|
+
if (options && options[:test_adapter])
|
261
|
+
faraday.adapter :test do |stub|
|
262
|
+
stub.post(options[:test_adapter][:path]) do |env|
|
263
|
+
options[:test_adapter][:func].call
|
264
|
+
end
|
265
|
+
stub.get(options[:test_adapter][:path]) do |env|
|
266
|
+
options[:test_adapter][:func].call
|
267
|
+
end
|
268
|
+
stub.delete(options[:test_adapter][:path]) do |env|
|
269
|
+
options[:test_adapter][:func].call
|
270
|
+
end
|
271
|
+
end
|
272
|
+
else
|
273
|
+
faraday.adapter :net_http
|
274
|
+
end
|
275
|
+
end
|
276
|
+
@api_key = api_key
|
277
|
+
end
|
278
|
+
|
279
|
+
def get_policy()
|
280
|
+
params = {}
|
281
|
+
data = nil
|
282
|
+
url = "/policy"
|
283
|
+
result = GET(url, params, data)
|
284
|
+
GetPolicyResult.new(**result)
|
285
|
+
end
|
286
|
+
|
287
|
+
def post_policy(data)
|
288
|
+
params = {}
|
289
|
+
data = OsoCloud::Helpers.to_hash(data)
|
290
|
+
url = "/policy"
|
291
|
+
result = POST(url, params, data)
|
292
|
+
ApiResult.new(**result)
|
293
|
+
end
|
294
|
+
|
295
|
+
def post_facts(data)
|
296
|
+
params = {}
|
297
|
+
data = OsoCloud::Helpers.to_hash(data)
|
298
|
+
url = "/facts"
|
299
|
+
result = POST(url, params, data)
|
300
|
+
Fact.new(**result)
|
301
|
+
end
|
302
|
+
|
303
|
+
def delete_facts(data)
|
304
|
+
params = {}
|
305
|
+
data = OsoCloud::Helpers.to_hash(data)
|
306
|
+
url = "/facts"
|
307
|
+
result = DELETE(url, params, data)
|
308
|
+
ApiResult.new(**result)
|
309
|
+
end
|
310
|
+
|
311
|
+
def post_bulk_load(data)
|
312
|
+
params = {}
|
313
|
+
data = OsoCloud::Helpers.to_hash(data)
|
314
|
+
url = "/bulk_load"
|
315
|
+
result = POST(url, params, data)
|
316
|
+
ApiResult.new(**result)
|
317
|
+
end
|
318
|
+
|
319
|
+
def post_bulk_delete(data)
|
320
|
+
params = {}
|
321
|
+
data = OsoCloud::Helpers.to_hash(data)
|
322
|
+
url = "/bulk_delete"
|
323
|
+
result = POST(url, params, data)
|
324
|
+
ApiResult.new(**result)
|
325
|
+
end
|
326
|
+
|
327
|
+
def post_bulk(data)
|
328
|
+
params = {}
|
329
|
+
data = OsoCloud::Helpers.to_hash(data)
|
330
|
+
url = "/bulk"
|
331
|
+
result = POST(url, params, data)
|
332
|
+
ApiResult.new(**result)
|
333
|
+
end
|
334
|
+
|
335
|
+
def post_authorize(data)
|
336
|
+
params = {}
|
337
|
+
data = OsoCloud::Helpers.to_hash(data)
|
338
|
+
url = "/authorize"
|
339
|
+
result = POST(url, params, data)
|
340
|
+
AuthorizeResult.new(**result)
|
341
|
+
end
|
342
|
+
|
343
|
+
def post_authorize_resources(data)
|
344
|
+
params = {}
|
345
|
+
data = OsoCloud::Helpers.to_hash(data)
|
346
|
+
url = "/authorize_resources"
|
347
|
+
result = POST(url, params, data)
|
348
|
+
AuthorizeResourcesResult.new(**result)
|
349
|
+
end
|
350
|
+
|
351
|
+
def post_list(data)
|
352
|
+
params = {}
|
353
|
+
data = OsoCloud::Helpers.to_hash(data)
|
354
|
+
url = "/list"
|
355
|
+
result = POST(url, params, data)
|
356
|
+
ListResult.new(**result)
|
357
|
+
end
|
358
|
+
|
359
|
+
def post_actions(data)
|
360
|
+
params = {}
|
361
|
+
data = OsoCloud::Helpers.to_hash(data)
|
362
|
+
url = "/actions"
|
363
|
+
result = POST(url, params, data)
|
364
|
+
ActionsResult.new(**result)
|
365
|
+
end
|
366
|
+
|
367
|
+
def post_query(data)
|
368
|
+
params = {}
|
369
|
+
data = OsoCloud::Helpers.to_hash(data)
|
370
|
+
url = "/query"
|
371
|
+
result = POST(url, params, data)
|
372
|
+
QueryResult.new(**result)
|
373
|
+
end
|
374
|
+
|
375
|
+
def get_stats()
|
376
|
+
params = {}
|
377
|
+
data = nil
|
378
|
+
url = "/stats"
|
379
|
+
result = GET(url, params, data)
|
380
|
+
StatsResult.new(**result)
|
381
|
+
end
|
382
|
+
|
383
|
+
def clear_data()
|
384
|
+
params = {}
|
385
|
+
data = nil
|
386
|
+
url = "/clear_data"
|
387
|
+
result = POST(url, params, data)
|
388
|
+
ApiResult.new(**result)
|
389
|
+
end
|
390
|
+
|
391
|
+
|
392
|
+
# hard-coded, not generated
|
393
|
+
def get_facts(predicate, args)
|
394
|
+
params = {}
|
395
|
+
params["predicate"] = predicate
|
396
|
+
args.each_with_index do |arg, i|
|
397
|
+
arg_query = OsoCloud::Helpers.extract_arg_query(arg)
|
398
|
+
if arg_query
|
399
|
+
params["args.#{i}.type"] = arg_query.type
|
400
|
+
params["args.#{i}.id"] = arg_query.id
|
401
|
+
end
|
402
|
+
end
|
403
|
+
data = nil
|
404
|
+
url = "/facts"
|
405
|
+
result = GET(url, params, data)
|
406
|
+
result.map { |v| Fact.new(**v) }
|
407
|
+
end
|
408
|
+
|
409
|
+
def headers()
|
410
|
+
{
|
411
|
+
"Authorization" => "Bearer %s" % @api_key,
|
412
|
+
"User-Agent" => "Oso Cloud (ruby)",
|
413
|
+
"Accept": "application/json",
|
414
|
+
"Content-Type": "application/json",
|
415
|
+
"X-OsoApiVersion": "0"
|
416
|
+
}
|
417
|
+
end
|
418
|
+
|
419
|
+
def GET(path, params, body)
|
420
|
+
response = @connection.get("api#{path}", params, headers )
|
421
|
+
handle_faraday_response response
|
422
|
+
rescue Faraday::Error => error
|
423
|
+
handle_faraday_error error
|
424
|
+
end
|
425
|
+
|
426
|
+
def POST(path, params, body)
|
427
|
+
response = @connection.post("api#{path}", body, headers) do |req|
|
428
|
+
req.params = params
|
429
|
+
end
|
430
|
+
handle_faraday_response response
|
431
|
+
rescue Faraday::Error => error
|
432
|
+
handle_faraday_error error
|
433
|
+
end
|
434
|
+
|
435
|
+
def DELETE(path, params, body)
|
436
|
+
response = @connection.delete("api#{path}", params, headers) do |req|
|
437
|
+
req.body = body
|
438
|
+
end
|
439
|
+
handle_faraday_response response
|
440
|
+
rescue Faraday::Error => error
|
441
|
+
handle_faraday_error error
|
442
|
+
end
|
443
|
+
|
444
|
+
def handle_faraday_response(response)
|
445
|
+
# TODO:(@patrickod) refactor duplicative JSON parsing
|
446
|
+
JSON.parse(response.env[:raw_body], symbolize_names: true)
|
447
|
+
end
|
448
|
+
|
449
|
+
def handle_faraday_error(error)
|
450
|
+
err = JSON.parse(error.response[:body], symbolize_names: true)
|
451
|
+
raise ApiError.new(**err)
|
452
|
+
rescue JSON::ParserError => e
|
453
|
+
raise ApiError.new(message: e.message)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
end
|
458
|
+
end
|
data/lib/oso/helpers.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
module OsoCloud
|
2
|
+
# @!visibility private
|
3
|
+
module Helpers
|
4
|
+
# @!visibility private
|
5
|
+
def self.extract_value(x)
|
6
|
+
return OsoCloud::Core::Value.new(type: "String", id: x) if x.is_a? String
|
7
|
+
|
8
|
+
return nil if x.nil?
|
9
|
+
|
10
|
+
type = (x.type.nil? ? nil : x.type.to_s)
|
11
|
+
id = (x.id.nil? ? nil : x.id.to_s)
|
12
|
+
OsoCloud::Core::Value.new(type: type, id: id)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @!visibility private
|
16
|
+
def self.extract_arg_query(x)
|
17
|
+
self.extract_value(x)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @!visibility private
|
21
|
+
def self.param_to_fact(predicate, args)
|
22
|
+
OsoCloud::Core::Fact.new(predicate: predicate, args: args.map { |a| self.extract_value(a) })
|
23
|
+
end
|
24
|
+
|
25
|
+
# @!visibility private
|
26
|
+
def self.params_to_facts(facts)
|
27
|
+
facts.map { |predicate, *args| self.param_to_fact(predicate, args) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.from_value(value)
|
31
|
+
if value.id.nil?
|
32
|
+
if value.type.nil?
|
33
|
+
nil
|
34
|
+
else
|
35
|
+
{ type: value.type }
|
36
|
+
end
|
37
|
+
else
|
38
|
+
if value.type == "String"
|
39
|
+
value.id
|
40
|
+
else
|
41
|
+
{ id: value.id, type: value.type }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @!visibility private
|
47
|
+
def self.to_hash(o)
|
48
|
+
return o.map { |v| self.to_hash(v) } if o.is_a? Array
|
49
|
+
return o if o.instance_variables.empty?
|
50
|
+
hash = {}
|
51
|
+
o.instance_variables.each { |var|
|
52
|
+
v = var.to_s.delete("@")
|
53
|
+
value = o.send(v)
|
54
|
+
hash[v] = self.to_hash(value)
|
55
|
+
}
|
56
|
+
hash
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/oso/oso.rb
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
require 'oso/version'
|
6
|
+
require 'oso/api'
|
7
|
+
require 'oso/helpers'
|
8
|
+
|
9
|
+
##
|
10
|
+
# For more detailed documentation, see
|
11
|
+
# https://www.osohq.com/docs/reference/client-apis/ruby
|
12
|
+
module OsoCloud
|
13
|
+
|
14
|
+
# Represents an object in your application, with a type and id.
|
15
|
+
# Both "type" and "id" should be strings.
|
16
|
+
Value = Struct::new(:type, :id, keyword_init: true) do
|
17
|
+
|
18
|
+
def to_api_value
|
19
|
+
OsoCloud::Helpers.extract_value(self)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Oso Cloud client for Ruby
|
24
|
+
#
|
25
|
+
# About facts:
|
26
|
+
#
|
27
|
+
# Some of these methods accept and return "fact"s.
|
28
|
+
# A "fact" is an array with at least one element.
|
29
|
+
# The first element must be a string, representing the fact's name.
|
30
|
+
# Any other elements in the array, which together represent the fact's arguments,
|
31
|
+
# can be "OsoCloud::Value" objects or strings.
|
32
|
+
class Oso
|
33
|
+
def initialize(url: 'https://cloud.osohq.com', api_key: nil)
|
34
|
+
@api = OsoCloud::Core::Api.new(url: url, api_key: api_key)
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Update the active policy
|
39
|
+
#
|
40
|
+
# Updates the active policy in Oso Cloud, The string passed into
|
41
|
+
# this method should be written in Polar.
|
42
|
+
#
|
43
|
+
# @param policy [String]
|
44
|
+
# @return [nil]
|
45
|
+
def policy(policy)
|
46
|
+
@api.post_policy(OsoCloud::Core::Policy.new(src: policy, filename: ""))
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Check a permission
|
52
|
+
#
|
53
|
+
# Returns true if the actor can perform the action on the resource;
|
54
|
+
# otherwise false.
|
55
|
+
#
|
56
|
+
# @param actor [OsoCloud::Value]
|
57
|
+
# @param action [String]
|
58
|
+
# @param resource [OsoCloud::Value]
|
59
|
+
# @param context_facts [Array<fact>]
|
60
|
+
# @return [Boolean]
|
61
|
+
# @see Oso more information about facts
|
62
|
+
def authorize(actor, action, resource, context_facts = [])
|
63
|
+
actor_typed_id = actor.to_api_value
|
64
|
+
resource_typed_id = resource.to_api_value
|
65
|
+
result = @api.post_authorize(OsoCloud::Core::AuthorizeQuery.new(
|
66
|
+
actor_type: actor_typed_id.type,
|
67
|
+
actor_id: actor_typed_id.id,
|
68
|
+
action: action,
|
69
|
+
resource_type: resource_typed_id.type,
|
70
|
+
resource_id: resource_typed_id.id,
|
71
|
+
context_facts: OsoCloud::Helpers.params_to_facts(context_facts)
|
72
|
+
))
|
73
|
+
result.allowed
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# Check authorized resources
|
78
|
+
#
|
79
|
+
# Returns a subset of the resource which an actor can perform
|
80
|
+
# a particular action. Ordering and duplicates, if any exist, are preserved.
|
81
|
+
#
|
82
|
+
# @param actor [OsoCloud::Value]
|
83
|
+
# @param action [String]
|
84
|
+
# @param resources [Array<OsoCloud::Value>]
|
85
|
+
# @param context_facts [Array<fact>]
|
86
|
+
# @return [Array<OsoCloud::Value>]
|
87
|
+
# @see Oso more information about facts
|
88
|
+
def authorize_resources(actor, action, resources, context_facts = [])
|
89
|
+
return [] if resources.nil?
|
90
|
+
return [] if resources.empty?
|
91
|
+
|
92
|
+
key = lambda do |type, id|
|
93
|
+
"#{type}:#{id}"
|
94
|
+
end
|
95
|
+
|
96
|
+
resources_extracted = resources.map(&:to_api_value)
|
97
|
+
actor_typed_id = actor.to_api_value
|
98
|
+
data = OsoCloud::Core::AuthorizeResourcesQuery.new(
|
99
|
+
actor_type: actor_typed_id.type, actor_id: actor_typed_id.id,
|
100
|
+
action: action,
|
101
|
+
resources: resources_extracted,
|
102
|
+
context_facts: OsoCloud::Helpers::params_to_facts(context_facts)
|
103
|
+
)
|
104
|
+
result = @api.post_authorize_resources(data)
|
105
|
+
|
106
|
+
return [] if result.results.empty?
|
107
|
+
|
108
|
+
results_lookup = Hash.new
|
109
|
+
result.results.each do |r|
|
110
|
+
k = key.call(r.type, r.id)
|
111
|
+
if results_lookup[k] == nil
|
112
|
+
results_lookup[k] = true
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
results = resources.select do |r|
|
117
|
+
e = r.to_api_value
|
118
|
+
exists = results_lookup[key.call(e.type, e.id)]
|
119
|
+
exists
|
120
|
+
end
|
121
|
+
results
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# List authorized resources
|
126
|
+
#
|
127
|
+
# Fetches a list of resource ids on which an actor can perform a
|
128
|
+
# particular action.
|
129
|
+
#
|
130
|
+
# @param actor [OsoCloud::Value]
|
131
|
+
# @param action [String]
|
132
|
+
# @param resource_type [String]
|
133
|
+
# @param context_facts [Array<fact>]
|
134
|
+
# @return [Array<String>]
|
135
|
+
# @see Oso more information about facts
|
136
|
+
def list(actor, action, resource_type, context_facts = [])
|
137
|
+
actor_typed_id = actor.to_api_value
|
138
|
+
result = @api.post_list(OsoCloud::Core::ListQuery.new(
|
139
|
+
actor_type: actor_typed_id.type,
|
140
|
+
actor_id: actor_typed_id.id,
|
141
|
+
action: action,
|
142
|
+
resource_type: resource_type,
|
143
|
+
context_facts: OsoCloud::Helpers.params_to_facts(context_facts)
|
144
|
+
))
|
145
|
+
result.results
|
146
|
+
end
|
147
|
+
|
148
|
+
##
|
149
|
+
# List authorized actions
|
150
|
+
#
|
151
|
+
# Fetches a list of actions which an actor can perform on a particular resource.
|
152
|
+
#
|
153
|
+
# @param actor [OsoCloud::Value]
|
154
|
+
# @param resource [OsoCloud::Value]
|
155
|
+
# @param context_facts [Array<fact>]
|
156
|
+
# @return [Array<String>]
|
157
|
+
# @see Oso more information about facts
|
158
|
+
def actions(actor, resource, context_facts = [])
|
159
|
+
actor_typed_id = actor.to_api_value
|
160
|
+
resource_typed_id = resource.to_api_value
|
161
|
+
result = @api.post_actions(OsoCloud::Core::ActionsQuery.new(
|
162
|
+
actor_type: actor_typed_id.type,
|
163
|
+
actor_id: actor_typed_id.id,
|
164
|
+
resource_type: resource_typed_id.type,
|
165
|
+
resource_id: resource_typed_id.id,
|
166
|
+
context_facts: OsoCloud::Helpers.params_to_facts(context_facts)
|
167
|
+
))
|
168
|
+
result.results
|
169
|
+
end
|
170
|
+
|
171
|
+
##
|
172
|
+
# Add a fact
|
173
|
+
#
|
174
|
+
# Adds a fact with the given name and arguments.
|
175
|
+
#
|
176
|
+
# @param name [String]
|
177
|
+
# @param args [*[String, OsoCloud::Value]]
|
178
|
+
# @return [nil]
|
179
|
+
def tell(name, *args)
|
180
|
+
typed_args = args.map { |a| OsoCloud::Helpers.extract_value(a)}
|
181
|
+
@api.post_facts(OsoCloud::Core::Fact.new(predicate: name, args: typed_args))
|
182
|
+
nil
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# Add many facts
|
187
|
+
#
|
188
|
+
# Adds many facts at once.
|
189
|
+
#
|
190
|
+
# @param facts [Array<fact>]
|
191
|
+
# @return [nil]
|
192
|
+
# @see Oso more information about facts
|
193
|
+
def bulk_tell(facts)
|
194
|
+
@api.post_bulk_load(OsoCloud::Helpers.params_to_facts(facts))
|
195
|
+
nil
|
196
|
+
end
|
197
|
+
|
198
|
+
##
|
199
|
+
# Delete fact
|
200
|
+
#
|
201
|
+
# Deletes a fact. Does not throw an error if the fact is not found.
|
202
|
+
#
|
203
|
+
# @param name [String]
|
204
|
+
# @param args [*[String, OsoCloud::Value]]
|
205
|
+
# @return [nil]
|
206
|
+
def delete(name, *args)
|
207
|
+
typed_args = args.map { |a| OsoCloud::Helpers.extract_value(a) }
|
208
|
+
@api.delete_facts(OsoCloud::Core::Fact.new(predicate: name, args: typed_args))
|
209
|
+
nil
|
210
|
+
end
|
211
|
+
|
212
|
+
##
|
213
|
+
# Delete many facts
|
214
|
+
#
|
215
|
+
# Deletes many facts at once. Does not throw an error when some of
|
216
|
+
# the facts are not found.
|
217
|
+
#
|
218
|
+
# @param facts [Array<fact>]
|
219
|
+
# @return [nil]
|
220
|
+
# @see Oso more information about facts
|
221
|
+
def bulk_delete(facts)
|
222
|
+
@api.post_bulk_delete(OsoCloud::Helpers.params_to_facts(facts))
|
223
|
+
nil
|
224
|
+
end
|
225
|
+
|
226
|
+
##
|
227
|
+
# List facts
|
228
|
+
#
|
229
|
+
# Lists facts that are stored in Oso Cloud. Can be used to check the existence
|
230
|
+
# of a particular fact, or used to fetch all facts that have a particular
|
231
|
+
# argument. nil arguments operate as wildcards.
|
232
|
+
#
|
233
|
+
# @param name [String]
|
234
|
+
# @param args [*[String, OsoCloud::Value, nil]]
|
235
|
+
# @return [Array<fact>]
|
236
|
+
# @see Oso more information about facts
|
237
|
+
def get(name, *args)
|
238
|
+
@api.get_facts(name, args).map do |f|
|
239
|
+
name = f.predicate
|
240
|
+
args = f.args.map do |a|
|
241
|
+
v = OsoCloud::Helpers.from_value(a)
|
242
|
+
if v.is_a? Hash
|
243
|
+
OsoCloud::Value.new(type: v[:type], id: v[:id])
|
244
|
+
else
|
245
|
+
v
|
246
|
+
end
|
247
|
+
end
|
248
|
+
[name, *args]
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
# TODO query, bulk
|
254
|
+
end
|
255
|
+
end
|
data/lib/oso/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module
|
2
|
-
VERSION = '0.
|
1
|
+
module OsoCloud
|
2
|
+
VERSION = '1.0.1'.freeze
|
3
3
|
end
|
data/lib/oso-cloud.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'oso/oso'
|
data/oso-cloud.gemspec
CHANGED
@@ -2,13 +2,14 @@ require_relative 'lib/oso/version'
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = 'oso-cloud'
|
5
|
-
spec.version =
|
5
|
+
spec.version = OsoCloud::VERSION
|
6
6
|
spec.authors = ['Oso Security, Inc.']
|
7
7
|
spec.email = ['support@osohq.com']
|
8
|
-
spec.summary = 'Oso
|
8
|
+
spec.summary = 'Oso Cloud Ruby client'
|
9
9
|
spec.homepage = 'https://www.osohq.com/'
|
10
|
+
spec.license = 'Apache-2.0'
|
10
11
|
|
11
|
-
spec.required_ruby_version = Gem::Requirement.new('>=
|
12
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0')
|
12
13
|
|
13
14
|
# Specify which files should be added to the gem when it is released.
|
14
15
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -19,5 +20,7 @@ Gem::Specification.new do |spec|
|
|
19
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
21
|
spec.require_paths = ['lib']
|
21
22
|
|
23
|
+
spec.add_dependency 'faraday', '~> 2.5.2'
|
24
|
+
spec.add_dependency 'faraday-retry', '~> 2.0.0'
|
22
25
|
spec.add_development_dependency 'minitest', '~> 5.15'
|
23
26
|
end
|
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oso-cloud
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.1
|
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:
|
11
|
+
date: 2023-03-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.5.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.5.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: faraday-retry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.0.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.0.0
|
13
41
|
- !ruby/object:Gem::Dependency
|
14
42
|
name: minitest
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,11 +66,15 @@ files:
|
|
38
66
|
- Rakefile
|
39
67
|
- bin/console
|
40
68
|
- bin/setup
|
41
|
-
- lib/oso
|
69
|
+
- lib/oso-cloud.rb
|
70
|
+
- lib/oso/api.rb
|
71
|
+
- lib/oso/helpers.rb
|
72
|
+
- lib/oso/oso.rb
|
42
73
|
- lib/oso/version.rb
|
43
74
|
- oso-cloud.gemspec
|
44
75
|
homepage: https://www.osohq.com/
|
45
|
-
licenses:
|
76
|
+
licenses:
|
77
|
+
- Apache-2.0
|
46
78
|
metadata: {}
|
47
79
|
post_install_message:
|
48
80
|
rdoc_options: []
|
@@ -52,15 +84,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
52
84
|
requirements:
|
53
85
|
- - ">="
|
54
86
|
- !ruby/object:Gem::Version
|
55
|
-
version:
|
87
|
+
version: 3.0.0
|
56
88
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
89
|
requirements:
|
58
90
|
- - ">="
|
59
91
|
- !ruby/object:Gem::Version
|
60
92
|
version: '0'
|
61
93
|
requirements: []
|
62
|
-
rubygems_version: 3.
|
94
|
+
rubygems_version: 3.2.33
|
63
95
|
signing_key:
|
64
96
|
specification_version: 4
|
65
|
-
summary: Oso
|
97
|
+
summary: Oso Cloud Ruby client
|
66
98
|
test_files: []
|
data/lib/oso/client.rb
DELETED
@@ -1,198 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'net/http'
|
3
|
-
require 'uri'
|
4
|
-
|
5
|
-
require 'oso/version'
|
6
|
-
|
7
|
-
module Oso
|
8
|
-
class Client
|
9
|
-
def initialize(url: 'https://cloud.osohq.com', api_key: nil)
|
10
|
-
@url = url
|
11
|
-
@api_key = api_key
|
12
|
-
end
|
13
|
-
|
14
|
-
def policy(policy)
|
15
|
-
POST('policy', { src: policy })
|
16
|
-
end
|
17
|
-
|
18
|
-
def authorize(actor, action, resource, context_facts = [])
|
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
|
-
context_facts: facts_to_params(context_facts)
|
26
|
-
})
|
27
|
-
allowed = result['allowed']
|
28
|
-
allowed
|
29
|
-
end
|
30
|
-
|
31
|
-
def authorize_resources(actor, action, resources, context_facts = [])
|
32
|
-
return [] if resources.nil?
|
33
|
-
return [] if resources.empty?
|
34
|
-
|
35
|
-
key = lambda do |type, id|
|
36
|
-
"#{type}:#{id}"
|
37
|
-
end
|
38
|
-
|
39
|
-
resources_extracted = resources.map { |r| extract_typed_id(r) }
|
40
|
-
actor_typed_id = extract_typed_id actor
|
41
|
-
result = POST('authorize_resources', {
|
42
|
-
actor_type: actor_typed_id.type, actor_id: actor_typed_id.id,
|
43
|
-
action: action,
|
44
|
-
resources: resources_extracted,
|
45
|
-
context_facts: facts_to_params(context_facts)
|
46
|
-
})
|
47
|
-
|
48
|
-
return [] if result['results'].empty?
|
49
|
-
|
50
|
-
results_lookup = Hash.new
|
51
|
-
result['results'].each do |r|
|
52
|
-
k = key.call(r['type'], r['id'])
|
53
|
-
if results_lookup[k] == nil
|
54
|
-
results_lookup[k] = true
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
results = resources.select do |r|
|
59
|
-
e = extract_typed_id(r)
|
60
|
-
exists = results_lookup[key.call(e.type, e.id)]
|
61
|
-
exists
|
62
|
-
end
|
63
|
-
results
|
64
|
-
end
|
65
|
-
|
66
|
-
def list(actor, action, resource_type, context_facts = [])
|
67
|
-
actor_typed_id = extract_typed_id actor
|
68
|
-
result = POST('list', {
|
69
|
-
actor_type: actor_typed_id.type, actor_id: actor_typed_id.id,
|
70
|
-
action: action,
|
71
|
-
resource_type: resource_type,
|
72
|
-
context_facts: facts_to_params(context_facts)
|
73
|
-
})
|
74
|
-
results = result['results']
|
75
|
-
results
|
76
|
-
end
|
77
|
-
|
78
|
-
def actions(actor, resource, context_facts = [])
|
79
|
-
actor_typed_id = extract_typed_id actor
|
80
|
-
resource_typed_id = extract_typed_id resource
|
81
|
-
result = POST('actions', {
|
82
|
-
actor_type: actor_typed_id.type, actor_id: actor_typed_id.id,
|
83
|
-
resource_type: resource_typed_id.type, resource_id: resource_typed_id.id,
|
84
|
-
context_facts: facts_to_params(context_facts)
|
85
|
-
})
|
86
|
-
results = result['results']
|
87
|
-
results
|
88
|
-
end
|
89
|
-
|
90
|
-
def tell(predicate, *args)
|
91
|
-
typed_args = args.map { |a| extract_typed_id a}
|
92
|
-
POST('facts', { predicate: predicate, args: typed_args })
|
93
|
-
end
|
94
|
-
|
95
|
-
def bulk_tell(facts)
|
96
|
-
POST('bulk_load', facts_to_params(facts))
|
97
|
-
end
|
98
|
-
|
99
|
-
def delete(predicate, *args)
|
100
|
-
typed_args = args.map { |a| extract_typed_id a}
|
101
|
-
DELETE('facts', { predicate: predicate, args: typed_args })
|
102
|
-
end
|
103
|
-
|
104
|
-
def bulk_delete(facts)
|
105
|
-
POST('bulk_delete', facts_to_params(facts))
|
106
|
-
end
|
107
|
-
|
108
|
-
def get(predicate, *args)
|
109
|
-
params = {predicate: predicate}
|
110
|
-
args.each_with_index do |arg, i|
|
111
|
-
typed_id = extract_arg_query(arg)
|
112
|
-
if typed_id
|
113
|
-
params["args.#{i}.type"] = typed_id.type
|
114
|
-
params["args.#{i}.id"] = typed_id.id
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
GET('facts', params)
|
119
|
-
end
|
120
|
-
|
121
|
-
private
|
122
|
-
|
123
|
-
def headers()
|
124
|
-
{
|
125
|
-
"Authorization" => "Basic %s" % @api_key,
|
126
|
-
"User-Agent" => "Oso Cloud (ruby)",
|
127
|
-
"Accept": "application/json",
|
128
|
-
"Content-Type": "application/json"
|
129
|
-
}
|
130
|
-
end
|
131
|
-
|
132
|
-
|
133
|
-
def GET(path, params)
|
134
|
-
uri = URI("#{@url}/api/#{path}")
|
135
|
-
uri.query = URI::encode_www_form(params)
|
136
|
-
use_ssl = (uri.scheme == 'https')
|
137
|
-
|
138
|
-
result = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl ) { |http|
|
139
|
-
http.request(Net::HTTP::Get.new(uri, headers)) {|r|
|
140
|
-
r.read_body
|
141
|
-
}
|
142
|
-
}
|
143
|
-
handle_result result
|
144
|
-
|
145
|
-
end
|
146
|
-
|
147
|
-
def POST(path, params)
|
148
|
-
result = Net::HTTP.post(URI("#{@url}/api/#{path}"), params.to_json, headers)
|
149
|
-
handle_result result
|
150
|
-
end
|
151
|
-
|
152
|
-
def DELETE(path, params)
|
153
|
-
uri = URI("#{@url}/api/#{path}")
|
154
|
-
use_ssl = (uri.scheme == 'https')
|
155
|
-
result = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl ) { |http|
|
156
|
-
http.request(Net::HTTP::Delete.new(uri, headers), params.to_json) {|r|
|
157
|
-
r.read_body
|
158
|
-
}
|
159
|
-
}
|
160
|
-
handle_result result
|
161
|
-
end
|
162
|
-
|
163
|
-
def handle_result(result)
|
164
|
-
unless result.is_a?(Net::HTTPSuccess)
|
165
|
-
raise "Got an unexpected error from Oso Service: #{result.code}\n#{result.body}"
|
166
|
-
end
|
167
|
-
|
168
|
-
JSON.parse(result.body)
|
169
|
-
end
|
170
|
-
|
171
|
-
def extract_typed_id(x)
|
172
|
-
return TypedId.new(type: "String", id: x) if x.is_a? String
|
173
|
-
|
174
|
-
raise "#{x} does not have an 'id' field" unless x.respond_to? :id
|
175
|
-
raise "Invalid 'id' field on #{x}: #{x.id}" if x.id.nil?
|
176
|
-
|
177
|
-
TypedId.new(type: x.class.name, id: x.id.to_s)
|
178
|
-
end
|
179
|
-
|
180
|
-
def extract_arg_query(x)
|
181
|
-
return nil if x.nil?
|
182
|
-
extract_typed_id(x)
|
183
|
-
end
|
184
|
-
|
185
|
-
def facts_to_params(facts)
|
186
|
-
facts.map { |predicate, *args|
|
187
|
-
typed_args = args.map { |a| extract_typed_id a}
|
188
|
-
{ predicate: predicate, args: typed_args }
|
189
|
-
}
|
190
|
-
end
|
191
|
-
|
192
|
-
TypedId = Struct.new(:type, :id, keyword_init: true) do
|
193
|
-
def to_json(*args)
|
194
|
-
to_h.to_json(*args)
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|