oso-cloud 0.6.0 → 1.0.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 +10 -1
- data/README.md +42 -14
- data/lib/oso/api.rb +416 -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: 460a79201e11b1f8e81db2eedea8bfd1c6651301336e1d31652a54a1ed537344
|
4
|
+
data.tar.gz: bd3692528348f8e9e45a36c09a3df41cef74fc47258db81471b456e0f1297f32
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 360042f1cb0f076651160473ae092215f36773832c70aa042e26a58a2ebbd9aacf034ec7e0227136075fbe1bc000148c627629a6ddee3ca0ba91cc422b1f2818
|
7
|
+
data.tar.gz: 79eeab9786a8358d12c3010648ab03f3fff454e69ae217e15122fbc5ebd5e2f4619111da3727e2bb60af14bd3f11e66989c8c74594243852ab131de0e8d1396d
|
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.0)
|
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.0)
|
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,416 @@
|
|
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
|
22
|
+
attr_reader :message
|
23
|
+
|
24
|
+
def initialize(message:)
|
25
|
+
@message = message
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @!visibility private
|
30
|
+
class Policy
|
31
|
+
attr_reader :filename
|
32
|
+
attr_reader :src
|
33
|
+
|
34
|
+
def initialize(filename:, src:)
|
35
|
+
@filename = filename
|
36
|
+
@src = src
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @!visibility private
|
41
|
+
class GetPolicyResult
|
42
|
+
attr_reader :policy
|
43
|
+
|
44
|
+
def initialize(policy:)
|
45
|
+
if policy.is_a? Policy
|
46
|
+
@policy = policy
|
47
|
+
else
|
48
|
+
@policy = Policy.new(**policy)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @!visibility private
|
54
|
+
class Fact
|
55
|
+
attr_reader :predicate
|
56
|
+
attr_reader :args
|
57
|
+
|
58
|
+
def initialize(predicate:, args:)
|
59
|
+
@predicate = predicate
|
60
|
+
@args = args.map { |v| if v.is_a? Value then v else Value.new(**v) end }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# @!visibility private
|
65
|
+
class Value
|
66
|
+
attr_reader :type
|
67
|
+
attr_reader :id
|
68
|
+
|
69
|
+
def initialize(type:, id:)
|
70
|
+
@type = type
|
71
|
+
@id = id
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# @!visibility private
|
76
|
+
class Bulk
|
77
|
+
attr_reader :delete
|
78
|
+
attr_reader :tell
|
79
|
+
|
80
|
+
def initialize(delete:, tell:)
|
81
|
+
@delete = delete.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
82
|
+
@tell = tell.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# @!visibility private
|
87
|
+
class AuthorizeResult
|
88
|
+
attr_reader :allowed
|
89
|
+
|
90
|
+
def initialize(allowed:)
|
91
|
+
@allowed = allowed
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# @!visibility private
|
96
|
+
class AuthorizeQuery
|
97
|
+
attr_reader :actor_type
|
98
|
+
attr_reader :actor_id
|
99
|
+
attr_reader :action
|
100
|
+
attr_reader :resource_type
|
101
|
+
attr_reader :resource_id
|
102
|
+
attr_reader :context_facts
|
103
|
+
|
104
|
+
def initialize(actor_type:, actor_id:, action:, resource_type:, resource_id:, context_facts:)
|
105
|
+
@actor_type = actor_type
|
106
|
+
@actor_id = actor_id
|
107
|
+
@action = action
|
108
|
+
@resource_type = resource_type
|
109
|
+
@resource_id = resource_id
|
110
|
+
@context_facts = context_facts.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# @!visibility private
|
115
|
+
class AuthorizeResourcesResult
|
116
|
+
attr_reader :results
|
117
|
+
|
118
|
+
def initialize(results:)
|
119
|
+
@results = results.map { |v| if v.is_a? Value then v else Value.new(**v) end }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# @!visibility private
|
124
|
+
class AuthorizeResourcesQuery
|
125
|
+
attr_reader :actor_type
|
126
|
+
attr_reader :actor_id
|
127
|
+
attr_reader :action
|
128
|
+
attr_reader :resources
|
129
|
+
attr_reader :context_facts
|
130
|
+
|
131
|
+
def initialize(actor_type:, actor_id:, action:, resources:, context_facts:)
|
132
|
+
@actor_type = actor_type
|
133
|
+
@actor_id = actor_id
|
134
|
+
@action = action
|
135
|
+
@resources = resources.map { |v| if v.is_a? Value then v else Value.new(**v) end }
|
136
|
+
@context_facts = context_facts.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# @!visibility private
|
141
|
+
class ListResult
|
142
|
+
attr_reader :results
|
143
|
+
|
144
|
+
def initialize(results:)
|
145
|
+
@results = results
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# @!visibility private
|
150
|
+
class ListQuery
|
151
|
+
attr_reader :actor_type
|
152
|
+
attr_reader :actor_id
|
153
|
+
attr_reader :action
|
154
|
+
attr_reader :resource_type
|
155
|
+
attr_reader :context_facts
|
156
|
+
|
157
|
+
def initialize(actor_type:, actor_id:, action:, resource_type:, context_facts:)
|
158
|
+
@actor_type = actor_type
|
159
|
+
@actor_id = actor_id
|
160
|
+
@action = action
|
161
|
+
@resource_type = resource_type
|
162
|
+
@context_facts = context_facts.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# @!visibility private
|
167
|
+
class ActionsResult
|
168
|
+
attr_reader :results
|
169
|
+
|
170
|
+
def initialize(results:)
|
171
|
+
@results = results
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# @!visibility private
|
176
|
+
class ActionsQuery
|
177
|
+
attr_reader :actor_type
|
178
|
+
attr_reader :actor_id
|
179
|
+
attr_reader :resource_type
|
180
|
+
attr_reader :resource_id
|
181
|
+
attr_reader :context_facts
|
182
|
+
|
183
|
+
def initialize(actor_type:, actor_id:, resource_type:, resource_id:, context_facts:)
|
184
|
+
@actor_type = actor_type
|
185
|
+
@actor_id = actor_id
|
186
|
+
@resource_type = resource_type
|
187
|
+
@resource_id = resource_id
|
188
|
+
@context_facts = context_facts.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# @!visibility private
|
193
|
+
class QueryResult
|
194
|
+
attr_reader :results
|
195
|
+
|
196
|
+
def initialize(results:)
|
197
|
+
@results = results.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# @!visibility private
|
202
|
+
class Query
|
203
|
+
attr_reader :fact
|
204
|
+
attr_reader :context_facts
|
205
|
+
|
206
|
+
def initialize(fact:, context_facts:)
|
207
|
+
if fact.is_a? Fact
|
208
|
+
@fact = fact
|
209
|
+
else
|
210
|
+
@fact = Fact.new(**fact)
|
211
|
+
end
|
212
|
+
@context_facts = context_facts.map { |v| if v.is_a? Fact then v else Fact.new(**v) end }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# @!visibility private
|
217
|
+
class StatsResult
|
218
|
+
attr_reader :num_roles
|
219
|
+
attr_reader :num_relations
|
220
|
+
attr_reader :num_facts
|
221
|
+
|
222
|
+
def initialize(num_roles:, num_relations:, num_facts:)
|
223
|
+
@num_roles = num_roles
|
224
|
+
@num_relations = num_relations
|
225
|
+
@num_facts = num_facts
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
# @!visibility private
|
231
|
+
class Api
|
232
|
+
def initialize(url: 'https://cloud.osohq.com', api_key: nil)
|
233
|
+
@url = url
|
234
|
+
@connection = Faraday.new(url: url) do |faraday|
|
235
|
+
faraday.request :retry, {
|
236
|
+
max: 15,
|
237
|
+
interval: 0.05,
|
238
|
+
interval_randomness: 0.5,
|
239
|
+
backoff_factor: 2,
|
240
|
+
retry_statuses: [429, 500, 502, 503, 504],
|
241
|
+
}
|
242
|
+
|
243
|
+
faraday.request :json
|
244
|
+
faraday.response :json, preserve_raw: true
|
245
|
+
faraday.adapter :net_http
|
246
|
+
end
|
247
|
+
@api_key = api_key
|
248
|
+
end
|
249
|
+
|
250
|
+
def get_policy()
|
251
|
+
params = {}
|
252
|
+
data = nil
|
253
|
+
url = "/policy"
|
254
|
+
result = GET(url, params, data)
|
255
|
+
GetPolicyResult.new(**result)
|
256
|
+
end
|
257
|
+
|
258
|
+
def post_policy(data)
|
259
|
+
params = {}
|
260
|
+
data = OsoCloud::Helpers.to_hash(data)
|
261
|
+
url = "/policy"
|
262
|
+
result = POST(url, params, data)
|
263
|
+
ApiResult.new(**result)
|
264
|
+
end
|
265
|
+
|
266
|
+
def post_facts(data)
|
267
|
+
params = {}
|
268
|
+
data = OsoCloud::Helpers.to_hash(data)
|
269
|
+
url = "/facts"
|
270
|
+
result = POST(url, params, data)
|
271
|
+
Fact.new(**result)
|
272
|
+
end
|
273
|
+
|
274
|
+
def delete_facts(data)
|
275
|
+
params = {}
|
276
|
+
data = OsoCloud::Helpers.to_hash(data)
|
277
|
+
url = "/facts"
|
278
|
+
result = DELETE(url, params, data)
|
279
|
+
ApiResult.new(**result)
|
280
|
+
end
|
281
|
+
|
282
|
+
def post_bulk_load(data)
|
283
|
+
params = {}
|
284
|
+
data = OsoCloud::Helpers.to_hash(data)
|
285
|
+
url = "/bulk_load"
|
286
|
+
result = POST(url, params, data)
|
287
|
+
ApiResult.new(**result)
|
288
|
+
end
|
289
|
+
|
290
|
+
def post_bulk_delete(data)
|
291
|
+
params = {}
|
292
|
+
data = OsoCloud::Helpers.to_hash(data)
|
293
|
+
url = "/bulk_delete"
|
294
|
+
result = POST(url, params, data)
|
295
|
+
ApiResult.new(**result)
|
296
|
+
end
|
297
|
+
|
298
|
+
def post_bulk(data)
|
299
|
+
params = {}
|
300
|
+
data = OsoCloud::Helpers.to_hash(data)
|
301
|
+
url = "/bulk"
|
302
|
+
result = POST(url, params, data)
|
303
|
+
ApiResult.new(**result)
|
304
|
+
end
|
305
|
+
|
306
|
+
def post_authorize(data)
|
307
|
+
params = {}
|
308
|
+
data = OsoCloud::Helpers.to_hash(data)
|
309
|
+
url = "/authorize"
|
310
|
+
result = POST(url, params, data)
|
311
|
+
AuthorizeResult.new(**result)
|
312
|
+
end
|
313
|
+
|
314
|
+
def post_authorize_resources(data)
|
315
|
+
params = {}
|
316
|
+
data = OsoCloud::Helpers.to_hash(data)
|
317
|
+
url = "/authorize_resources"
|
318
|
+
result = POST(url, params, data)
|
319
|
+
AuthorizeResourcesResult.new(**result)
|
320
|
+
end
|
321
|
+
|
322
|
+
def post_list(data)
|
323
|
+
params = {}
|
324
|
+
data = OsoCloud::Helpers.to_hash(data)
|
325
|
+
url = "/list"
|
326
|
+
result = POST(url, params, data)
|
327
|
+
ListResult.new(**result)
|
328
|
+
end
|
329
|
+
|
330
|
+
def post_actions(data)
|
331
|
+
params = {}
|
332
|
+
data = OsoCloud::Helpers.to_hash(data)
|
333
|
+
url = "/actions"
|
334
|
+
result = POST(url, params, data)
|
335
|
+
ActionsResult.new(**result)
|
336
|
+
end
|
337
|
+
|
338
|
+
def post_query(data)
|
339
|
+
params = {}
|
340
|
+
data = OsoCloud::Helpers.to_hash(data)
|
341
|
+
url = "/query"
|
342
|
+
result = POST(url, params, data)
|
343
|
+
QueryResult.new(**result)
|
344
|
+
end
|
345
|
+
|
346
|
+
def get_stats()
|
347
|
+
params = {}
|
348
|
+
data = nil
|
349
|
+
url = "/stats"
|
350
|
+
result = GET(url, params, data)
|
351
|
+
StatsResult.new(**result)
|
352
|
+
end
|
353
|
+
|
354
|
+
def clear_data()
|
355
|
+
params = {}
|
356
|
+
data = nil
|
357
|
+
url = "/clear_data"
|
358
|
+
result = POST(url, params, data)
|
359
|
+
ApiResult.new(**result)
|
360
|
+
end
|
361
|
+
|
362
|
+
|
363
|
+
# hard-coded, not generated
|
364
|
+
def get_facts(predicate, args)
|
365
|
+
params = {}
|
366
|
+
params["predicate"] = predicate
|
367
|
+
args.each_with_index do |arg, i|
|
368
|
+
arg_query = OsoCloud::Helpers.extract_arg_query(arg)
|
369
|
+
if arg_query
|
370
|
+
params["args.#{i}.type"] = arg_query.type
|
371
|
+
params["args.#{i}.id"] = arg_query.id
|
372
|
+
end
|
373
|
+
end
|
374
|
+
data = nil
|
375
|
+
url = "/facts"
|
376
|
+
result = GET(url, params, data)
|
377
|
+
result.map { |v| Fact.new(**v) }
|
378
|
+
end
|
379
|
+
|
380
|
+
def headers()
|
381
|
+
{
|
382
|
+
"Authorization" => "Bearer %s" % @api_key,
|
383
|
+
"User-Agent" => "Oso Cloud (ruby)",
|
384
|
+
"Accept": "application/json",
|
385
|
+
"Content-Type": "application/json",
|
386
|
+
"X-OsoApiVersion": "0"
|
387
|
+
}
|
388
|
+
end
|
389
|
+
|
390
|
+
def GET(path, params, body)
|
391
|
+
response = @connection.get("api#{path}", params, headers )
|
392
|
+
handle_faraday_response response
|
393
|
+
end
|
394
|
+
|
395
|
+
def POST(path, params, body)
|
396
|
+
response = @connection.post("api#{path}", body, headers) do |req|
|
397
|
+
req.params = params
|
398
|
+
end
|
399
|
+
handle_faraday_response response
|
400
|
+
end
|
401
|
+
|
402
|
+
def DELETE(path, params, body)
|
403
|
+
response = @connection.delete("api#{path}", params, headers) do |req|
|
404
|
+
req.body = body
|
405
|
+
end
|
406
|
+
handle_faraday_response response
|
407
|
+
end
|
408
|
+
|
409
|
+
def handle_faraday_response(response)
|
410
|
+
# TODO:(@patrickod) refactor duplicative JSON parsing
|
411
|
+
JSON.parse(response.env[:raw_body], symbolize_names: true)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
end
|
416
|
+
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.0'.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.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-
|
11
|
+
date: 2022-09-27 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
|