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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 32e04bc53de8b9a2a8820365219be2b0e345e765
4
- data.tar.gz: 911eea4f97fe5aba8cb0f97485e7c9c86c68772e
3
+ metadata.gz: aa0564b86f2ceb4b54a62cdbae1cf2a3cc14ee91
4
+ data.tar.gz: d0cbc579ac5bd7284b559d3ca3d31af9f6837e96
5
5
  SHA512:
6
- metadata.gz: 70a6be55d3f0291aa53ff067dfdeea504cd261b40f2aeb4c613887c78fb7cf8d86de91e34f169c2e1198fa0f1c49a999dda8d4d608b9e61ca3011cb72925a8cf
7
- data.tar.gz: e23bca87576a43d12e54d4206fbb50595f4bc6d8c0c06eb7a090c2fad5978c3a5f63f851d974ec5ec2469f6b9b20246826c58f29654d34ea5fe5fa8ef197d6cd
6
+ metadata.gz: d2a7f469cff84febf8dd66f737384523fd576d42b1d44e41247bb738a7b26ca230d9e91066be2acf2a6ade07be780aeec5fa529b21c88c7d0bb626ca66ab821a
7
+ data.tar.gz: 742452956eeb29797b173888a268962bbc6e92e88b7148e7ca3952bb9d9c7687abd55d74c657d170056a61e911ed0183d29fa3ef471aa5c8cd733f47d72d188b
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  vendor
10
+ active_record_test.db
10
11
 
11
12
  # rspec failure tracking
12
13
  .rspec_status
data/Gemfile.lock CHANGED
@@ -1,24 +1,40 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- oso-oso (0.15.1)
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.1)
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
Binary file
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
@@ -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.
@@ -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
@@ -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
- @classes = {}
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
- @classes = other.classes.dup
78
+ @types = other.types.dup
64
79
  @instances = other.instances.dup
65
80
  end
66
81
 
67
- # Fetch a Ruby class from the {#classes} cache.
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 classes.key? name
88
+ raise UnregisteredClassError, name unless types.key? name
74
89
 
75
- classes[name].get
90
+ types[name].klass.get
76
91
  end
77
92
 
78
- # Store a Ruby class in the {#classes} cache.
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 classes.key? name
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
- classes[name] = PolarClass.new(cls)
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
  #
@@ -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(new_host.to_polar(query))
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: new_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
- query(Predicate.new(name, args: args))
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
 
@@ -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
@@ -10,6 +10,7 @@ require 'oso/polar/predicate'
10
10
  require 'oso/polar/query'
11
11
  require 'oso/polar/query_event'
12
12
  require 'oso/polar/variable'
13
+ require 'oso/polar/data_filtering'
13
14
 
14
15
  module Oso
15
16
  # Top-level namespace for Polar language library.
data/lib/oso/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Oso
4
- VERSION = '0.15.1'
4
+ VERSION = '0.20.0-beta'
5
5
  end
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.15.1
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-09-03 00:00:00.000000000 Z
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: '0'
201
+ version: 1.3.1
173
202
  requirements: []
174
203
  rubyforge_project:
175
204
  rubygems_version: 2.6.14.4