oso-oso 0.15.1 → 0.20.0.pre.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile.lock +24 -2
- data/ext/oso-oso/lib/libpolar.dylib +0 -0
- data/ext/oso-oso/lib/libpolar.so +0 -0
- data/ext/oso-oso/lib/polar.dll +0 -0
- data/lib/oso/polar/data_filtering.rb +155 -0
- data/lib/oso/polar/ffi/polar.rb +16 -0
- data/lib/oso/polar/ffi/query.rb +6 -0
- data/lib/oso/polar/host.rb +62 -11
- data/lib/oso/polar/polar.rb +62 -8
- data/lib/oso/polar/query.rb +76 -1
- data/lib/oso/polar.rb +1 -0
- data/lib/oso/version.rb +1 -1
- data/oso-oso.gemspec +2 -0
- metadata +33 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa0564b86f2ceb4b54a62cdbae1cf2a3cc14ee91
|
4
|
+
data.tar.gz: d0cbc579ac5bd7284b559d3ca3d31af9f6837e96
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2a7f469cff84febf8dd66f737384523fd576d42b1d44e41247bb738a7b26ca230d9e91066be2acf2a6ade07be780aeec5fa529b21c88c7d0bb626ca66ab821a
|
7
|
+
data.tar.gz: 742452956eeb29797b173888a268962bbc6e92e88b7148e7ca3952bb9d9c7687abd55d74c657d170056a61e911ed0183d29fa3ef471aa5c8cd733f47d72d188b
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,24 +1,40 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
oso-oso (0.
|
4
|
+
oso-oso (0.20.0.pre.beta)
|
5
5
|
ffi (~> 1.0)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
+
activemodel (5.2.6)
|
11
|
+
activesupport (= 5.2.6)
|
12
|
+
activerecord (5.2.6)
|
13
|
+
activemodel (= 5.2.6)
|
14
|
+
activesupport (= 5.2.6)
|
15
|
+
arel (>= 9.0)
|
16
|
+
activesupport (5.2.6)
|
17
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
|
+
i18n (>= 0.7, < 2)
|
19
|
+
minitest (~> 5.1)
|
20
|
+
tzinfo (~> 1.1)
|
21
|
+
arel (9.0.0)
|
10
22
|
ast (2.4.1)
|
11
23
|
backport (1.1.2)
|
12
24
|
benchmark (0.1.0)
|
13
25
|
byebug (11.1.3)
|
14
26
|
coderay (1.1.3)
|
27
|
+
concurrent-ruby (1.1.9)
|
15
28
|
diff-lcs (1.4.4)
|
16
29
|
e2mmap (0.1.0)
|
17
|
-
ffi (1.15.
|
30
|
+
ffi (1.15.3)
|
31
|
+
i18n (1.8.10)
|
32
|
+
concurrent-ruby (~> 1.0)
|
18
33
|
jaro_winkler (1.5.4)
|
19
34
|
maruku (0.7.3)
|
20
35
|
method_source (1.0.0)
|
21
36
|
mini_portile2 (2.4.0)
|
37
|
+
minitest (5.14.4)
|
22
38
|
nokogiri (1.10.10)
|
23
39
|
mini_portile2 (~> 2.4.0)
|
24
40
|
parallel (1.19.2)
|
@@ -75,8 +91,12 @@ GEM
|
|
75
91
|
thor (~> 1.0)
|
76
92
|
tilt (~> 2.0)
|
77
93
|
yard (~> 0.9, >= 0.9.24)
|
94
|
+
sqlite3 (1.4.2)
|
78
95
|
thor (1.0.1)
|
96
|
+
thread_safe (0.3.6)
|
79
97
|
tilt (2.0.10)
|
98
|
+
tzinfo (1.2.9)
|
99
|
+
thread_safe (~> 0.1)
|
80
100
|
unicode-display_width (1.7.0)
|
81
101
|
yard (0.9.25)
|
82
102
|
|
@@ -84,12 +104,14 @@ PLATFORMS
|
|
84
104
|
ruby
|
85
105
|
|
86
106
|
DEPENDENCIES
|
107
|
+
activerecord
|
87
108
|
oso-oso!
|
88
109
|
pry-byebug (~> 3.9.0)
|
89
110
|
rake (~> 12.0)
|
90
111
|
rspec (~> 3.0)
|
91
112
|
rubocop (~> 0.89.1)
|
92
113
|
solargraph (~> 0.39.14)
|
114
|
+
sqlite3
|
93
115
|
yard (~> 0.9.25)
|
94
116
|
|
95
117
|
BUNDLED WITH
|
Binary file
|
data/ext/oso-oso/lib/libpolar.so
CHANGED
Binary file
|
data/ext/oso-oso/lib/polar.dll
CHANGED
Binary file
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Oso
|
4
|
+
module Polar
|
5
|
+
# Data filtering interface for Ruby
|
6
|
+
module DataFiltering
|
7
|
+
# Represents a set of filter sequences that should allow the host
|
8
|
+
# to obtain the records satisfying a query.
|
9
|
+
class FilterPlan
|
10
|
+
include Enumerable
|
11
|
+
attr_reader :result_sets
|
12
|
+
|
13
|
+
def initialize(polar, partials, class_name)
|
14
|
+
types = polar.host.serialize_types
|
15
|
+
parsed_json = polar.ffi.build_filter_plan(types, partials, 'resource', class_name)
|
16
|
+
@polar = polar
|
17
|
+
@result_sets = parsed_json['result_sets'].map do |rset|
|
18
|
+
ResultSet.new polar, rset
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def each(&blk)
|
23
|
+
result_sets.each(&blk)
|
24
|
+
end
|
25
|
+
|
26
|
+
def resolve # rubocop:disable Metrics/AbcSize
|
27
|
+
reduce([]) do |acc, rs|
|
28
|
+
requests = rs.requests
|
29
|
+
acc + rs.resolve_order.each_with_object({}) do |i, set_results|
|
30
|
+
req = requests[i]
|
31
|
+
constraints = req.constraints
|
32
|
+
constraints.each { |c| c.ground set_results }
|
33
|
+
set_results[i] = @polar.host.types[req.class_tag].fetcher[constraints]
|
34
|
+
end[rs.result_id]
|
35
|
+
end.uniq
|
36
|
+
end
|
37
|
+
|
38
|
+
# Represents a sequence of filters for one set of results
|
39
|
+
class ResultSet
|
40
|
+
attr_reader :requests, :resolve_order, :result_id
|
41
|
+
|
42
|
+
def initialize(polar, parsed_json)
|
43
|
+
@resolve_order = parsed_json['resolve_order']
|
44
|
+
@result_id = parsed_json['result_id']
|
45
|
+
@requests = parsed_json['requests'].each_with_object({}) do |req, reqs|
|
46
|
+
reqs[req[0].to_i] = Request.new(polar, req[1])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Represents a filter for a result set
|
51
|
+
class Request
|
52
|
+
attr_reader :constraints, :class_tag
|
53
|
+
|
54
|
+
def initialize(polar, parsed_json)
|
55
|
+
@constraints = parsed_json['constraints'].map do |con|
|
56
|
+
Constraint.parse polar, con
|
57
|
+
end
|
58
|
+
@class_tag = parsed_json['class_tag']
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Represents relationships between resources, eg. parent/child
|
65
|
+
class Relationship
|
66
|
+
attr_reader :kind, :other_type, :my_field, :other_field
|
67
|
+
|
68
|
+
def initialize(kind:, other_type:, my_field:, other_field:)
|
69
|
+
@kind = kind
|
70
|
+
@other_type = other_type
|
71
|
+
@my_field = my_field
|
72
|
+
@other_field = other_field
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Represents field-field relationships on one resource.
|
77
|
+
class Field
|
78
|
+
attr_reader :field
|
79
|
+
|
80
|
+
def initialize(field:)
|
81
|
+
@field = field
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Represents field-field relationships on different resources.
|
86
|
+
class Ref
|
87
|
+
attr_reader :field, :result_id
|
88
|
+
|
89
|
+
def initialize(field:, result_id:)
|
90
|
+
@field = field
|
91
|
+
@result_id = result_id
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Represents a condition that must hold on a resource.
|
96
|
+
class Constraint
|
97
|
+
attr_reader :kind, :field, :value
|
98
|
+
|
99
|
+
CHECKS = {
|
100
|
+
'Eq' => ->(a, b) { a == b },
|
101
|
+
'In' => ->(a, b) { b.include? a },
|
102
|
+
'Contains' => ->(a, b) { a.include? b }
|
103
|
+
}.freeze
|
104
|
+
|
105
|
+
def initialize(kind:, field:, value:)
|
106
|
+
@kind = kind
|
107
|
+
@field = field
|
108
|
+
@value = value
|
109
|
+
@check = CHECKS[kind]
|
110
|
+
raise "Unknown constraint kind `#{kind}`" if @check.nil?
|
111
|
+
end
|
112
|
+
|
113
|
+
def ground(results)
|
114
|
+
return unless value.is_a? Ref
|
115
|
+
|
116
|
+
ref = value
|
117
|
+
@value = results[ref.result_id]
|
118
|
+
@value = value.map { |v| v.send ref.field } unless ref.field.nil?
|
119
|
+
end
|
120
|
+
|
121
|
+
def check(item)
|
122
|
+
val = value.is_a?(Field) ? item.send(value.field) : value
|
123
|
+
item = field.nil? ? item : item.send(field)
|
124
|
+
@check[item, val]
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.parse(polar, constraint) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
128
|
+
kind = constraint['kind']
|
129
|
+
raise unless %w[Eq In Contains].include? kind
|
130
|
+
|
131
|
+
field = constraint['field']
|
132
|
+
value = constraint['value']
|
133
|
+
|
134
|
+
value_kind = value.keys.first
|
135
|
+
value = value[value_kind]
|
136
|
+
|
137
|
+
case value_kind
|
138
|
+
when 'Term'
|
139
|
+
value = polar.host.to_ruby value
|
140
|
+
when 'Ref'
|
141
|
+
child_field = value['field']
|
142
|
+
result_id = value['result_id']
|
143
|
+
value = Ref.new field: child_field, result_id: result_id
|
144
|
+
when 'Field'
|
145
|
+
value = Field.new field: value
|
146
|
+
else
|
147
|
+
raise "Unknown value kind `#{value_kind}`"
|
148
|
+
end
|
149
|
+
|
150
|
+
new kind: kind, field: field, value: value
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
data/lib/oso/polar/ffi/polar.rb
CHANGED
@@ -25,6 +25,12 @@ module Oso
|
|
25
25
|
attach_function :register_constant, :polar_register_constant, [FFI::Polar, :string, :string], :int32
|
26
26
|
attach_function :next_message, :polar_next_polar_message, [FFI::Polar], FFI::Message
|
27
27
|
attach_function :free, :polar_free, [FFI::Polar], :int32
|
28
|
+
attach_function(
|
29
|
+
:build_filter_plan,
|
30
|
+
:polar_build_filter_plan,
|
31
|
+
[FFI::Polar, :string, :string, :string, :string],
|
32
|
+
:string
|
33
|
+
)
|
28
34
|
end
|
29
35
|
private_constant :Rust
|
30
36
|
|
@@ -51,6 +57,16 @@ module Oso
|
|
51
57
|
handle_error if result.zero?
|
52
58
|
end
|
53
59
|
|
60
|
+
def build_filter_plan(types, partials, variable, class_tag)
|
61
|
+
types = JSON.dump(types)
|
62
|
+
partials = JSON.dump(partials)
|
63
|
+
plan = Rust.build_filter_plan(self, types, partials, variable, class_tag)
|
64
|
+
process_messages
|
65
|
+
handle_error if plan.nil?
|
66
|
+
# TODO(gw) more error checking?
|
67
|
+
JSON.parse plan
|
68
|
+
end
|
69
|
+
|
54
70
|
# @param src [String]
|
55
71
|
# @param filename [String]
|
56
72
|
# @raise [FFI::Error] if the FFI call returns an error.
|
data/lib/oso/polar/ffi/query.rb
CHANGED
@@ -21,6 +21,7 @@ module Oso
|
|
21
21
|
attach_function :next_message, :polar_next_query_message, [FFI::Query], FFI::Message
|
22
22
|
attach_function :source, :polar_query_source_info, [FFI::Query], FFI::Source
|
23
23
|
attach_function :free, :query_free, [FFI::Query], :int32
|
24
|
+
attach_function :bind, :polar_bind, [FFI::Query, :string, :string], :int32
|
24
25
|
end
|
25
26
|
private_constant :Rust
|
26
27
|
|
@@ -67,6 +68,11 @@ module Oso
|
|
67
68
|
::Oso::Polar::QueryEvent.new(JSON.parse(event.to_s))
|
68
69
|
end
|
69
70
|
|
71
|
+
def bind(name, value)
|
72
|
+
res = Rust.bind(self, name, JSON.dump(value))
|
73
|
+
handle_error if res.zero?
|
74
|
+
end
|
75
|
+
|
70
76
|
def next_message
|
71
77
|
Rust.next_message(self)
|
72
78
|
end
|
data/lib/oso/polar/host.rb
CHANGED
@@ -34,14 +34,29 @@ module Oso
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
# For holding type metadata: name, fields, etc.
|
38
|
+
class UserType
|
39
|
+
attr_reader :name, :klass, :id, :fields, :fetcher
|
40
|
+
|
41
|
+
def initialize(name:, klass:, id:, fields:, fetcher:)
|
42
|
+
@name = name
|
43
|
+
@klass = klass
|
44
|
+
@id = id
|
45
|
+
# accept symbol keys
|
46
|
+
@fields = fields.each_with_object({}) { |kv, o| o[kv[0].to_s] = kv[1] }
|
47
|
+
@fetcher = fetcher
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
37
51
|
# Translate between Polar and the host language (Ruby).
|
38
52
|
class Host # rubocop:disable Metrics/ClassLength
|
53
|
+
# @return [Hash<String, Class>]
|
54
|
+
attr_reader :types
|
55
|
+
|
39
56
|
protected
|
40
57
|
|
41
58
|
# @return [FFI::Polar]
|
42
59
|
attr_reader :ffi_polar
|
43
|
-
# @return [Hash<String, Class>]
|
44
|
-
attr_reader :classes
|
45
60
|
# @return [Hash<Integer, Object>]
|
46
61
|
attr_reader :instances
|
47
62
|
# @return [Boolean]
|
@@ -53,39 +68,45 @@ module Oso
|
|
53
68
|
|
54
69
|
def initialize(ffi_polar)
|
55
70
|
@ffi_polar = ffi_polar
|
56
|
-
@
|
71
|
+
@types = {}
|
57
72
|
@instances = {}
|
58
73
|
@accept_expression = false
|
59
74
|
end
|
60
75
|
|
61
76
|
def initialize_copy(other)
|
62
77
|
@ffi_polar = other.ffi_polar
|
63
|
-
@
|
78
|
+
@types = other.types.dup
|
64
79
|
@instances = other.instances.dup
|
65
80
|
end
|
66
81
|
|
67
|
-
# Fetch a Ruby class from the {#
|
82
|
+
# Fetch a Ruby class from the {#types} cache.
|
68
83
|
#
|
69
84
|
# @param name [String]
|
70
85
|
# @return [Class]
|
71
86
|
# @raise [UnregisteredClassError] if the class has not been registered.
|
72
87
|
def get_class(name)
|
73
|
-
raise UnregisteredClassError, name unless
|
88
|
+
raise UnregisteredClassError, name unless types.key? name
|
74
89
|
|
75
|
-
|
90
|
+
types[name].klass.get
|
76
91
|
end
|
77
92
|
|
78
|
-
# Store a Ruby class in the {#
|
93
|
+
# Store a Ruby class in the {#types} cache.
|
79
94
|
#
|
80
95
|
# @param cls [Class] the class to cache.
|
81
96
|
# @param name [String] the name to cache the class as.
|
82
97
|
# @return [String] the name the class is cached as.
|
83
98
|
# @raise [DuplicateClassAliasError] if attempting to register a class
|
84
99
|
# under a previously-registered name.
|
85
|
-
def cache_class(cls, name:)
|
86
|
-
raise DuplicateClassAliasError.new name: name, old: get_class(name), new: cls if
|
100
|
+
def cache_class(cls, name:, fields: {}, fetcher: nil)
|
101
|
+
raise DuplicateClassAliasError.new name: name, old: get_class(name), new: cls if types.key? name
|
87
102
|
|
88
|
-
|
103
|
+
types[name] = types[cls] = UserType.new(
|
104
|
+
name: name,
|
105
|
+
klass: PolarClass.new(cls),
|
106
|
+
id: cache_instance(cls),
|
107
|
+
fields: fields,
|
108
|
+
fetcher: fetcher
|
109
|
+
)
|
89
110
|
name
|
90
111
|
end
|
91
112
|
|
@@ -190,6 +211,10 @@ module Oso
|
|
190
211
|
left_index && right_index && left_index < right_index
|
191
212
|
end
|
192
213
|
|
214
|
+
def subclass?(left_tag:, right_tag:)
|
215
|
+
get_class(left_tag) <= get_class(right_tag)
|
216
|
+
end
|
217
|
+
|
193
218
|
# Check if instance is an instance of class.
|
194
219
|
#
|
195
220
|
# @param instance [Hash<String, Object>]
|
@@ -201,6 +226,32 @@ module Oso
|
|
201
226
|
instance.is_a? cls
|
202
227
|
end
|
203
228
|
|
229
|
+
def serialize_types # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
230
|
+
polar_types = {}
|
231
|
+
types.values.uniq.each do |typ|
|
232
|
+
tag = typ.name
|
233
|
+
fields = typ.fields
|
234
|
+
field_types = {}
|
235
|
+
fields.each do |k, v|
|
236
|
+
field_types[k] =
|
237
|
+
if v.is_a? ::Oso::Polar::DataFiltering::Relationship
|
238
|
+
{
|
239
|
+
'Relationship' => {
|
240
|
+
'kind' => v.kind,
|
241
|
+
'other_class_tag' => v.other_type,
|
242
|
+
'my_field' => v.my_field,
|
243
|
+
'other_field' => v.other_field
|
244
|
+
}
|
245
|
+
}
|
246
|
+
else
|
247
|
+
{ 'Base' => { 'class_tag' => types[v].name } }
|
248
|
+
end
|
249
|
+
end
|
250
|
+
polar_types[tag] = field_types
|
251
|
+
end
|
252
|
+
polar_types
|
253
|
+
end
|
254
|
+
|
204
255
|
# Turn a Ruby value into a Polar term that's ready to be sent across the
|
205
256
|
# FFI boundary.
|
206
257
|
#
|
data/lib/oso/polar/polar.rb
CHANGED
@@ -52,6 +52,10 @@ module Oso
|
|
52
52
|
register_class String
|
53
53
|
end
|
54
54
|
|
55
|
+
def ffi
|
56
|
+
@ffi_polar
|
57
|
+
end
|
58
|
+
|
55
59
|
def enable_roles # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
56
60
|
return if polar_roles_enabled
|
57
61
|
|
@@ -88,6 +92,55 @@ module Oso
|
|
88
92
|
ffi_polar.validate_roles_config(validation_query_results)
|
89
93
|
end
|
90
94
|
|
95
|
+
# get the (maybe user-supplied) name of a class.
|
96
|
+
# kind of a hack because of class autoreloading.
|
97
|
+
def get_class_name(klass) # rubocop:disable Metrics/AbcSize
|
98
|
+
if host.types.key? klass
|
99
|
+
host.types[klass].name
|
100
|
+
elsif host.types.key? klass.name
|
101
|
+
host.types[klass.name].name
|
102
|
+
else
|
103
|
+
rec = host.types.values.find { |v| v.klass.get == klass }
|
104
|
+
raise "Unknown class `#{klass}`" if rec.nil?
|
105
|
+
|
106
|
+
host.types[klass] = rec
|
107
|
+
rec.name
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_allowed_resources(actor, action, klass) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
112
|
+
resource = Variable.new 'resource'
|
113
|
+
class_name = get_class_name klass
|
114
|
+
constraint = Expression.new(
|
115
|
+
'And',
|
116
|
+
[Expression.new('Isa', [resource, Pattern.new(class_name, {})])]
|
117
|
+
)
|
118
|
+
|
119
|
+
results = query_rule(
|
120
|
+
'allow',
|
121
|
+
actor,
|
122
|
+
action,
|
123
|
+
resource,
|
124
|
+
bindings: { 'resource' => constraint },
|
125
|
+
accept_expression: true
|
126
|
+
)
|
127
|
+
|
128
|
+
complete = []
|
129
|
+
partial = []
|
130
|
+
|
131
|
+
results.to_a.each do |result|
|
132
|
+
result.to_a.each do |key, val|
|
133
|
+
if val.is_a? Expression
|
134
|
+
partial.push({ 'bindings' => { key => host.to_polar(val) } })
|
135
|
+
else
|
136
|
+
complete.push val
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
filter = ::Oso::Polar::DataFiltering::FilterPlan.new(self, partial, class_name)
|
141
|
+
complete + filter.resolve
|
142
|
+
end
|
143
|
+
|
91
144
|
# Clear all rules and rule sources from the current Polar instance
|
92
145
|
#
|
93
146
|
# @return [self] for chaining.
|
@@ -150,17 +203,16 @@ module Oso
|
|
150
203
|
# @param query [Predicate]
|
151
204
|
# @return [Enumerator] of resulting bindings
|
152
205
|
# @raise [Error] if the FFI call raises one.
|
153
|
-
def query(query)
|
154
|
-
new_host = host.dup
|
206
|
+
def query(query, host: self.host.dup, bindings: {})
|
155
207
|
case query
|
156
208
|
when String
|
157
209
|
ffi_query = ffi_polar.new_query_from_str(query)
|
158
210
|
when Predicate
|
159
|
-
ffi_query = ffi_polar.new_query_from_term(
|
211
|
+
ffi_query = ffi_polar.new_query_from_term(host.to_polar(query))
|
160
212
|
else
|
161
213
|
raise InvalidQueryTypeError
|
162
214
|
end
|
163
|
-
Query.new(ffi_query, host:
|
215
|
+
Query.new(ffi_query, host: host, bindings: bindings)
|
164
216
|
end
|
165
217
|
|
166
218
|
# Query for a rule.
|
@@ -169,8 +221,10 @@ module Oso
|
|
169
221
|
# @param args [Array<Object>]
|
170
222
|
# @return [Enumerator] of resulting bindings
|
171
223
|
# @raise [Error] if the FFI call raises one.
|
172
|
-
def query_rule(name, *args)
|
173
|
-
|
224
|
+
def query_rule(name, *args, accept_expression: false, bindings: {})
|
225
|
+
host = self.host.dup
|
226
|
+
host.accept_expression = accept_expression
|
227
|
+
query(Predicate.new(name, args: args), host: host, bindings: bindings)
|
174
228
|
end
|
175
229
|
|
176
230
|
# Register a Ruby class with Polar.
|
@@ -181,8 +235,8 @@ module Oso
|
|
181
235
|
# under a previously-registered name.
|
182
236
|
# @raise [FFI::Error] if the FFI call returns an error.
|
183
237
|
# @return [self] for chaining.
|
184
|
-
def register_class(cls, name: nil)
|
185
|
-
name = host.cache_class(cls, name: name || cls.name)
|
238
|
+
def register_class(cls, name: nil, fields: {}, fetcher: nil)
|
239
|
+
name = host.cache_class(cls, name: name || cls.name, fields: fields, fetcher: fetcher)
|
186
240
|
register_constant(cls, name: name)
|
187
241
|
end
|
188
242
|
|
data/lib/oso/polar/query.rb
CHANGED
@@ -10,11 +10,12 @@ module Oso
|
|
10
10
|
|
11
11
|
# @param ffi_query [FFI::Query]
|
12
12
|
# @param host [Oso::Polar::Host]
|
13
|
-
def initialize(ffi_query, host:)
|
13
|
+
def initialize(ffi_query, host:, bindings: {})
|
14
14
|
@calls = {}
|
15
15
|
@ffi_query = ffi_query
|
16
16
|
ffi_query.enrich_message = host.method(:enrich_message)
|
17
17
|
@host = host
|
18
|
+
bindings.each { |k, v| ffi_query.bind k, host.to_polar(v) }
|
18
19
|
end
|
19
20
|
|
20
21
|
private
|
@@ -71,6 +72,9 @@ module Oso
|
|
71
72
|
# @raise [Error] if the FFI call raises one.
|
72
73
|
def handle_call(attribute, call_id:, instance:, args:, kwargs:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
73
74
|
instance = host.to_ruby(instance)
|
75
|
+
rel = get_relationship(instance.class, attribute)
|
76
|
+
return handle_relationship(call_id, instance, rel) unless rel.nil?
|
77
|
+
|
74
78
|
args = args.map { |a| host.to_ruby(a) }
|
75
79
|
kwargs = Hash[kwargs.map { |k, v| [k.to_sym, host.to_ruby(v)] }]
|
76
80
|
# The kwargs.empty? check is for Ruby < 2.7.
|
@@ -86,6 +90,40 @@ module Oso
|
|
86
90
|
call_result(nil, call_id: call_id)
|
87
91
|
end
|
88
92
|
|
93
|
+
# Get the type information for a field on a class.
|
94
|
+
#
|
95
|
+
# @param cls [UserType]
|
96
|
+
# @param tag [String]
|
97
|
+
# @return [UserType]
|
98
|
+
# @raise [Error] if no information is found
|
99
|
+
def get_field(cls, tag) # rubocop:disable Metrics/AbcSize
|
100
|
+
raise unless cls.fields.key? tag
|
101
|
+
|
102
|
+
ref = cls.fields[tag]
|
103
|
+
return host.types[ref] unless ref.is_a? ::Oso::Polar::DataFiltering::Relationship
|
104
|
+
|
105
|
+
case ref.kind
|
106
|
+
when 'parent'
|
107
|
+
host.types[ref.other_type]
|
108
|
+
when 'children'
|
109
|
+
host.types[Array]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Check if a series of dot operations on a base class yield an
|
114
|
+
# instance of another class.
|
115
|
+
def handle_external_isa_with_path(data) # rubocop:disable Metrics/AbcSize
|
116
|
+
sup = host.types[data['class_tag']]
|
117
|
+
bas = host.types[data['base_tag']]
|
118
|
+
path = data['path'].map(&host.method(:to_ruby))
|
119
|
+
sub = path.reduce(bas) { |cls, tag| get_field(cls, tag) }
|
120
|
+
answer = sub.klass.get <= sup.klass.get
|
121
|
+
question_result(answer, call_id: data['call_id'])
|
122
|
+
rescue StandardError => e
|
123
|
+
application_error e.message
|
124
|
+
question_result(nil, call_id: data['call_id'])
|
125
|
+
end
|
126
|
+
|
89
127
|
def handle_next_external(call_id, iterable)
|
90
128
|
unless calls.key? call_id
|
91
129
|
value = host.to_ruby iterable
|
@@ -129,6 +167,8 @@ module Oso
|
|
129
167
|
yield event.data['bindings'].transform_values { |v| host.to_ruby(v) }
|
130
168
|
when 'MakeExternal'
|
131
169
|
handle_make_external(event.data)
|
170
|
+
when 'ExternalIsaWithPath'
|
171
|
+
handle_external_isa_with_path(event.data)
|
132
172
|
when 'ExternalCall'
|
133
173
|
call_id = event.data['call_id']
|
134
174
|
instance = event.data['instance']
|
@@ -142,6 +182,12 @@ module Oso
|
|
142
182
|
right_tag = event.data['right_class_tag']
|
143
183
|
answer = host.subspecializer?(instance_id, left_tag: left_tag, right_tag: right_tag)
|
144
184
|
question_result(answer, call_id: event.data['call_id'])
|
185
|
+
when 'ExternalIsSubclass'
|
186
|
+
call_id = event.data['call_id']
|
187
|
+
left = event.data['left_class_tag']
|
188
|
+
right = event.data['right_class_tag']
|
189
|
+
answer = host.subclass?(left_tag: left, right_tag: right)
|
190
|
+
question_result(answer, call_id: call_id)
|
145
191
|
when 'ExternalIsa'
|
146
192
|
instance = event.data['instance']
|
147
193
|
class_tag = event.data['class_tag']
|
@@ -175,6 +221,35 @@ module Oso
|
|
175
221
|
end
|
176
222
|
end
|
177
223
|
end
|
224
|
+
|
225
|
+
def get_relationship(cls, attr)
|
226
|
+
typ = host.types[cls]
|
227
|
+
return unless typ
|
228
|
+
|
229
|
+
rel = typ.fields[attr]
|
230
|
+
return unless rel.is_a? ::Oso::Polar::DataFiltering::Relationship
|
231
|
+
|
232
|
+
rel
|
233
|
+
end
|
234
|
+
|
235
|
+
def handle_relationship(call_id, instance, rel) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
236
|
+
fetcher = host.types[rel.other_type].fetcher
|
237
|
+
constraint = ::Oso::Polar::DataFiltering::Constraint.new(
|
238
|
+
kind: 'Eq',
|
239
|
+
field: rel.other_field,
|
240
|
+
value: instance.send(rel.my_field)
|
241
|
+
)
|
242
|
+
res = fetcher[[constraint]].uniq
|
243
|
+
|
244
|
+
if rel.kind == 'parent'
|
245
|
+
raise "multiple parents: #{res}" unless res.length == 1
|
246
|
+
|
247
|
+
res = res[0]
|
248
|
+
end
|
249
|
+
|
250
|
+
res = JSON.dump host.to_polar res
|
251
|
+
call_result(res, call_id: call_id)
|
252
|
+
end
|
178
253
|
end
|
179
254
|
end
|
180
255
|
end
|
data/lib/oso/polar.rb
CHANGED
data/lib/oso/version.rb
CHANGED
data/oso-oso.gemspec
CHANGED
@@ -31,10 +31,12 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.add_runtime_dependency 'ffi', '~> 1.0'
|
32
32
|
|
33
33
|
# Development dependencies
|
34
|
+
spec.add_development_dependency 'activerecord'
|
34
35
|
spec.add_development_dependency 'pry-byebug', '~> 3.9.0'
|
35
36
|
spec.add_development_dependency 'rake', '~> 12.0'
|
36
37
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
37
38
|
spec.add_development_dependency 'rubocop', '~> 0.89.1'
|
38
39
|
spec.add_development_dependency 'solargraph', '~> 0.39.14'
|
40
|
+
spec.add_development_dependency 'sqlite3'
|
39
41
|
spec.add_development_dependency 'yard', '~> 0.9.25'
|
40
42
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oso-oso
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.20.0.pre.beta
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oso Security, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-08-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: pry-byebug
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +108,20 @@ dependencies:
|
|
94
108
|
- - "~>"
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: 0.39.14
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sqlite3
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
97
125
|
- !ruby/object:Gem::Dependency
|
98
126
|
name: yard
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -132,6 +160,7 @@ files:
|
|
132
160
|
- lib/oso.rb
|
133
161
|
- lib/oso/oso.rb
|
134
162
|
- lib/oso/polar.rb
|
163
|
+
- lib/oso/polar/data_filtering.rb
|
135
164
|
- lib/oso/polar/errors.rb
|
136
165
|
- lib/oso/polar/expression.rb
|
137
166
|
- lib/oso/polar/ffi.rb
|
@@ -167,9 +196,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
167
196
|
version: 2.4.0
|
168
197
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
198
|
requirements:
|
170
|
-
- - "
|
199
|
+
- - ">"
|
171
200
|
- !ruby/object:Gem::Version
|
172
|
-
version:
|
201
|
+
version: 1.3.1
|
173
202
|
requirements: []
|
174
203
|
rubyforge_project:
|
175
204
|
rubygems_version: 2.6.14.4
|