oso-oso 0.13.1 → 0.15.0

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: ab6ed22597c52c4a77ff24aa6455492ba9cf7ed7
4
- data.tar.gz: 3aaa0186592b86e63d9a2dcb2976f03154e0dcda
3
+ metadata.gz: 12fca0670b29961bafc90ea1565f5ef2e501a778
4
+ data.tar.gz: ca3fee786141d350b6d79e67ab042e650a082566
5
5
  SHA512:
6
- metadata.gz: 9e7ce6bbf44da47f40c4cc5b53fab5d17870125dd5eed7443be858d9f8e0adb98fcce7799eb7fb404c867d3e10fdb5522b52d69ebc5ac6ffa280f11ad56a9623
7
- data.tar.gz: d032a1742948a1f21f8fd19576aaf99479ef534a2247bac4e7a13e9f2910292a0b74eed56cef67deece467e2ae85ba6dc31df6b3a0d2761618703647895ae19a
6
+ metadata.gz: edcc9dd56153cc927d2791dfe398264c538030f44e7a1e36e9157b37bacc05033ad31f24d6d0c0f95b71a8de166633bcd24755e94fcb90683fee70a02711d0c2
7
+ data.tar.gz: e3155aef370a1d61c388f0342ee5a692afad0a473a362e740e1f8124afc86d3d18ae080e7acac856b081b7fc7e27d50b56b5e273d51e7cc1359092896480e92b
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- oso-oso (0.13.1)
4
+ oso-oso (0.15.0)
5
5
  ffi (~> 1.0)
6
6
 
7
7
  GEM
Binary file
Binary file
Binary file
data/lib/oso/polar.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'oso/polar/errors'
4
+ require 'oso/polar/expression'
4
5
  require 'oso/polar/ffi'
5
6
  require 'oso/polar/host'
7
+ require 'oso/polar/pattern'
6
8
  require 'oso/polar/polar'
7
9
  require 'oso/polar/predicate'
8
10
  require 'oso/polar/query'
@@ -100,8 +100,11 @@ module Oso
100
100
  class ApiError < Error; end
101
101
  class ParameterError < ApiError; end
102
102
 
103
+ class ValidationError < Error; end
104
+ class RolesValidationError < Error; end
105
+
103
106
  UNEXPECTED_EXPRESSION_MESSAGE = <<~MSG
104
- Recieved Expression from Polar VM. The Expression type is not yet supported in this language.
107
+ Received Expression from Polar VM. The Expression type is not yet supported in this language.
105
108
 
106
109
  This may mean you performed an operation in your policy over an unbound variable.
107
110
  MSG
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Oso
4
+ module Polar
5
+ # Polar expression.
6
+ class Expression
7
+ attr_reader :operator, :args
8
+
9
+ # @param operator [String]
10
+ # @param args [Array<Object>]
11
+ def initialize(operator, args)
12
+ @operator = operator
13
+ @args = args
14
+ end
15
+
16
+ # @param other [Expression]
17
+ # @return [Boolean]
18
+ def ==(other)
19
+ operator == other.operator && args == other.args
20
+ end
21
+
22
+ # @see #==
23
+ alias eql? ==
24
+ end
25
+ end
26
+ end
@@ -24,14 +24,29 @@ module Oso
24
24
  #
25
25
  # @return [::Oso::Polar::Error] if there's an FFI error.
26
26
  # @return [::Oso::Polar::FFIErrorNotFound] if there isn't one.
27
- def self.get # rubocop:disable Metrics/MethodLength
27
+ def self.get(enrich_message) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
28
28
  error = Rust.get
29
29
  return ::Oso::Polar::FFIErrorNotFound if error.null?
30
30
 
31
31
  error = JSON.parse(error.to_s)
32
32
  msg = error['formatted']
33
33
  kind, body = error['kind'].first
34
- subkind, details = body.first
34
+
35
+ # Not all errors have subkind and details.
36
+ # TODO (gj): This bug may exist in other libraries.
37
+ if body.is_a? Hash
38
+ subkind, details = body.first
39
+ else
40
+ subkind, details = nil
41
+ end
42
+
43
+ # Enrich error message and stack trace
44
+ msg = enrich_message.call(msg) if msg
45
+ if details
46
+ details['stack_trace'] = enrich_message.call(details['stack_trace']) if details['stack_trace']
47
+ details['msg'] = enrich_message.call(details['msg']) if details['msg']
48
+ end
49
+
35
50
  case kind
36
51
  when 'Parse'
37
52
  parse_error(subkind, msg: msg, details: details)
@@ -41,6 +56,8 @@ module Oso
41
56
  operational_error(subkind, msg: msg, details: details)
42
57
  when 'Parameter'
43
58
  api_error(subkind, msg: msg, details: details)
59
+ when 'RolesValidation'
60
+ validation_error(msg, details: details)
44
61
  end
45
62
  end
46
63
 
@@ -121,6 +138,16 @@ module Oso
121
138
  ::Oso::Polar::ApiError.new(msg, details: details)
122
139
  end
123
140
  end
141
+
142
+ # Map FFI Validation errors into Ruby exceptions.
143
+ #
144
+ # @param msg [String]
145
+ # @param details [Hash<String, Object>]
146
+ # @return [::Oso::Polar::ValidationError] the object converted into the expected format.
147
+ private_class_method def self.validation_error(msg, details:)
148
+ # This is currently the only type of validation error.
149
+ ::Oso::Polar::RolesValidationError.new(msg, details: details)
150
+ end
124
151
  end
125
152
  end
126
153
  end
@@ -19,10 +19,11 @@ module Oso
19
19
  attach_function :free, :string_free, [Message], :int32
20
20
  end
21
21
 
22
- def process
22
+ def process(enrich_message)
23
23
  message = JSON.parse(to_s)
24
24
  kind = message['kind']
25
25
  msg = message['msg']
26
+ msg = enrich_message.call(msg)
26
27
 
27
28
  case kind
28
29
  when 'Print'
@@ -7,11 +7,15 @@ module Oso
7
7
  module FFI
8
8
  # Wrapper class for Polar FFI pointer + operations.
9
9
  class Polar < ::FFI::AutoPointer
10
+ attr_accessor :enrich_message
11
+
10
12
  Rust = Module.new do
11
13
  extend ::FFI::Library
12
14
  ffi_lib FFI::LIB_PATH
13
15
 
14
16
  attach_function :new, :polar_new, [], FFI::Polar
17
+ attach_function :enable_roles, :polar_enable_roles, [FFI::Polar], :int32
18
+ attach_function :validate_roles_config, :polar_validate_roles_config, [FFI::Polar, :string], :int32
15
19
  attach_function :load, :polar_load, [FFI::Polar, :string, :string], :int32
16
20
  attach_function :clear_rules, :polar_clear_rules, [FFI::Polar], :int32
17
21
  attach_function :next_inline_query, :polar_next_inline_query, [FFI::Polar, :uint32], FFI::Query
@@ -28,25 +32,39 @@ module Oso
28
32
  # @raise [FFI::Error] if the FFI call returns an error.
29
33
  def self.create
30
34
  polar = Rust.new
31
- raise FFI::Error.get if polar.null?
35
+ handle_error if polar.null?
32
36
 
33
37
  polar
34
38
  end
35
39
 
40
+ # @raise [FFI::Error] if the FFI call returns an error.
41
+ def enable_roles
42
+ result = Rust.enable_roles(self)
43
+ process_messages
44
+ handle_error if result.zero?
45
+ end
46
+
47
+ # @raise [FFI::Error] if the FFI call returns an error.
48
+ def validate_roles_config(config)
49
+ result = Rust.validate_roles_config(self, JSON.dump(config))
50
+ process_messages
51
+ handle_error if result.zero?
52
+ end
53
+
36
54
  # @param src [String]
37
55
  # @param filename [String]
38
56
  # @raise [FFI::Error] if the FFI call returns an error.
39
57
  def load(src, filename: nil)
40
58
  loaded = Rust.load(self, src, filename)
41
59
  process_messages
42
- raise FFI::Error.get if loaded.zero?
60
+ handle_error if loaded.zero?
43
61
  end
44
62
 
45
63
  # @raise [FFI::Error] if the FFI call returns an error.
46
64
  def clear_rules
47
65
  cleared = Rust.clear_rules(self)
48
66
  process_messages
49
- raise FFI::Error.get if cleared.zero?
67
+ handle_error if cleared.zero?
50
68
  end
51
69
 
52
70
  # @return [FFI::Query] if there are remaining inline queries.
@@ -64,7 +82,7 @@ module Oso
64
82
  id = Rust.new_id(self)
65
83
  # TODO(gj): I don't think this error check is correct. If getting a new ID fails on the
66
84
  # Rust side, it'll probably surface as a panic (e.g., the KB lock is poisoned).
67
- raise FFI::Error.get if id.zero?
85
+ handle_error if id.zero?
68
86
 
69
87
  id
70
88
  end
@@ -75,7 +93,7 @@ module Oso
75
93
  def new_query_from_str(str)
76
94
  query = Rust.new_query_from_str(self, str, 0)
77
95
  process_messages
78
- raise FFI::Error.get if query.null?
96
+ handle_error if query.null?
79
97
 
80
98
  query
81
99
  end
@@ -86,7 +104,7 @@ module Oso
86
104
  def new_query_from_term(term)
87
105
  query = Rust.new_query_from_term(self, JSON.dump(term), 0)
88
106
  process_messages
89
- raise FFI::Error.get if query.null?
107
+ handle_error if query.null?
90
108
 
91
109
  query
92
110
  end
@@ -96,7 +114,7 @@ module Oso
96
114
  # @raise [FFI::Error] if the FFI call returns an error.
97
115
  def register_constant(value, name:)
98
116
  registered = Rust.register_constant(self, name, JSON.dump(value))
99
- raise FFI::Error.get if registered.zero?
117
+ handle_error if registered.zero?
100
118
  end
101
119
 
102
120
  def next_message
@@ -108,9 +126,13 @@ module Oso
108
126
  message = next_message
109
127
  break if message.null?
110
128
 
111
- message.process
129
+ message.process(enrich_message)
112
130
  end
113
131
  end
132
+
133
+ def handle_error
134
+ raise FFI::Error.get(enrich_message)
135
+ end
114
136
  end
115
137
  end
116
138
  end
@@ -7,6 +7,8 @@ module Oso
7
7
  module FFI
8
8
  # Wrapper class for Query FFI pointer + operations.
9
9
  class Query < ::FFI::AutoPointer
10
+ attr_accessor :enrich_message
11
+
10
12
  Rust = Module.new do
11
13
  extend ::FFI::Library
12
14
  ffi_lib FFI::LIB_PATH
@@ -27,7 +29,7 @@ module Oso
27
29
  def debug_command(cmd)
28
30
  res = Rust.debug_command(self, cmd)
29
31
  process_messages
30
- raise FFI::Error.get if res.zero?
32
+ handle_error if res.zero?
31
33
  end
32
34
 
33
35
  # @param result [String]
@@ -35,7 +37,7 @@ module Oso
35
37
  # @raise [FFI::Error] if the FFI call returns an error.
36
38
  def call_result(result, call_id:)
37
39
  res = Rust.call_result(self, call_id, result)
38
- raise FFI::Error.get if res.zero?
40
+ handle_error if res.zero?
39
41
  end
40
42
 
41
43
  # @param result [Boolean]
@@ -44,7 +46,7 @@ module Oso
44
46
  def question_result(result, call_id:)
45
47
  result = result ? 1 : 0
46
48
  res = Rust.question_result(self, call_id, result)
47
- raise FFI::Error.get if res.zero?
49
+ handle_error if res.zero?
48
50
  end
49
51
 
50
52
  # @param result [Boolean]
@@ -52,7 +54,7 @@ module Oso
52
54
  # @raise [FFI::Error] if the FFI call returns an error.
53
55
  def application_error(message)
54
56
  res = Rust.application_error(self, message)
55
- raise FFI::Error.get if res.zero?
57
+ handle_error if res.zero?
56
58
  end
57
59
 
58
60
  # @return [::Oso::Polar::QueryEvent]
@@ -60,7 +62,7 @@ module Oso
60
62
  def next_event
61
63
  event = Rust.next_event(self)
62
64
  process_messages
63
- raise FFI::Error.get if event.null?
65
+ handle_error if event.null?
64
66
 
65
67
  ::Oso::Polar::QueryEvent.new(JSON.parse(event.to_s))
66
68
  end
@@ -74,7 +76,7 @@ module Oso
74
76
  message = next_message
75
77
  break if message.null?
76
78
 
77
- message.process
79
+ message.process(enrich_message)
78
80
  end
79
81
  end
80
82
 
@@ -82,10 +84,14 @@ module Oso
82
84
  # @raise [FFI::Error] if the FFI call returns an error.
83
85
  def source
84
86
  res = Rust.source(self)
85
- raise FFI::Error.get if res.null?
87
+ handle_error if res.null?
86
88
 
87
89
  res.to_s
88
90
  end
91
+
92
+ def handle_error
93
+ raise FFI::Error.get(enrich_message)
94
+ end
89
95
  end
90
96
  end
91
97
  end
@@ -2,6 +2,38 @@
2
2
 
3
3
  module Oso
4
4
  module Polar
5
+ # Ruby code reloaders (i.e. the one used by rails) swap out the value of
6
+ # a constant on code changes. Because of this, we can't reliably call
7
+ # `is_a?` on the constant that was passed to `register_class`.
8
+ #
9
+ # Example (where Foo is a class defined in foo.rb):
10
+ # > klass = Foo
11
+ # > Foo.new.is_a? klass
12
+ # => true
13
+ # > ... user changes foo.rb ...
14
+ # > Foo.new.is_a? klass
15
+ # => false
16
+ #
17
+ # To solve this, when we need to access the class (e.g. during isa), we
18
+ # look it up using const_get, which will always return the up-to-date
19
+ # version of the class.
20
+ class PolarClass
21
+ attr_reader :name, :anon_class
22
+
23
+ def initialize(klass)
24
+ @name = klass.name
25
+ # If the class doesn't have a name, it is anonymous, meaning we should
26
+ # actually store it directly
27
+ @anon_class = klass if klass.name.nil?
28
+ end
29
+
30
+ def get
31
+ return anon_class if anon_class
32
+
33
+ Object.const_get(name)
34
+ end
35
+ end
36
+
5
37
  # Translate between Polar and the host language (Ruby).
6
38
  class Host # rubocop:disable Metrics/ClassLength
7
39
  protected
@@ -12,13 +44,18 @@ module Oso
12
44
  attr_reader :classes
13
45
  # @return [Hash<Integer, Object>]
14
46
  attr_reader :instances
47
+ # @return [Boolean]
48
+ attr_reader :accept_expression
15
49
 
16
50
  public
17
51
 
52
+ attr_writer :accept_expression
53
+
18
54
  def initialize(ffi_polar)
19
55
  @ffi_polar = ffi_polar
20
56
  @classes = {}
21
57
  @instances = {}
58
+ @accept_expression = false
22
59
  end
23
60
 
24
61
  def initialize_copy(other)
@@ -35,7 +72,7 @@ module Oso
35
72
  def get_class(name)
36
73
  raise UnregisteredClassError, name unless classes.key? name
37
74
 
38
- classes[name]
75
+ classes[name].get
39
76
  end
40
77
 
41
78
  # Store a Ruby class in the {#classes} cache.
@@ -48,7 +85,7 @@ module Oso
48
85
  def cache_class(cls, name:)
49
86
  raise DuplicateClassAliasError.new name: name, old: get_class(name), new: cls if classes.key? name
50
87
 
51
- classes[name] = cls
88
+ classes[name] = PolarClass.new(cls)
52
89
  name
53
90
  end
54
91
 
@@ -73,7 +110,10 @@ module Oso
73
110
  def get_instance(id)
74
111
  raise UnregisteredInstanceError, id unless instance? id
75
112
 
76
- instances[id]
113
+ instance = instances[id]
114
+ return instance.get if instance.is_a? PolarClass
115
+
116
+ instance
77
117
  end
78
118
 
79
119
  # Cache a Ruby instance in the {#instances} cache, fetching a new id if
@@ -84,6 +124,8 @@ module Oso
84
124
  # @return [Integer] the instance ID.
85
125
  def cache_instance(instance, id: nil)
86
126
  id = ffi_polar.new_id if id.nil?
127
+ # Save the instance as a PolarClass if it is a non-anonymous class
128
+ instance = PolarClass.new(instance) if instance.is_a?(Class)
87
129
  instances[id] = instance
88
130
  id
89
131
  end
@@ -107,6 +149,33 @@ module Oso
107
149
  raise PolarRuntimeError, "Error constructing instance of #{cls_name}: #{e}"
108
150
  end
109
151
 
152
+ OPS = {
153
+ 'Lt' => :<,
154
+ 'Gt' => :>,
155
+ 'Eq' => :==,
156
+ 'Geq' => :>=,
157
+ 'Leq' => :<=,
158
+ 'Neq' => :!=
159
+ }.freeze
160
+
161
+ # Compare two values
162
+ #
163
+ # @param op [String] operation to perform.
164
+ # @param args [Array<Object>] left and right args to operation.
165
+ # @raise [PolarRuntimeError] if operation fails or is unsupported.
166
+ # @return [Boolean]
167
+ def operator(operation, args)
168
+ left, right = args
169
+ op = OPS[operation]
170
+ raise PolarRuntimeError, "Unsupported external operation '#{left.class} #{operation} #{right.class}'" if op.nil?
171
+
172
+ begin
173
+ left.__send__ op, right
174
+ rescue StandardError
175
+ raise PolarRuntimeError, "External operation '#{left.class} #{operation} #{right.class}' failed."
176
+ end
177
+ end
178
+
110
179
  # Check if the left class is more specific than the right class
111
180
  # with respect to the given instance.
112
181
  #
@@ -132,17 +201,6 @@ module Oso
132
201
  instance.is_a? cls
133
202
  end
134
203
 
135
- # Check if two instances unify
136
- #
137
- # @param left_instance_id [Integer]
138
- # @param right_instance_id [Integer]
139
- # @return [Boolean]
140
- def unify?(left_instance_id, right_instance_id)
141
- left_instance = get_instance(left_instance_id)
142
- right_instance = get_instance(right_instance_id)
143
- left_instance == right_instance
144
- end
145
-
146
204
  # Turn a Ruby value into a Polar term that's ready to be sent across the
147
205
  # FFI boundary.
148
206
  #
@@ -173,9 +231,18 @@ module Oso
173
231
  { 'Call' => { 'name' => value.name, 'args' => value.args.map { |el| to_polar(el) } } }
174
232
  when value.instance_of?(Variable)
175
233
  # This is supported so that we can query for unbound variables
176
- { 'Variable' => value }
234
+ { 'Variable' => value.name }
235
+ when value.instance_of?(Expression)
236
+ { 'Expression' => { 'operator' => value.operator, 'args' => value.args.map { |el| to_polar(el) } } }
237
+ when value.instance_of?(Pattern)
238
+ dict = to_polar(value.fields)['value']
239
+ if value.tag.nil?
240
+ { 'Pattern' => dict }
241
+ else
242
+ { 'Pattern' => { 'Instance' => { 'tag' => value.tag, 'fields' => dict['Dictionary'] } } }
243
+ end
177
244
  else
178
- { 'ExternalInstance' => { 'instance_id' => cache_instance(value), 'repr' => value.to_s } }
245
+ { 'ExternalInstance' => { 'instance_id' => cache_instance(value), 'repr' => nil } }
179
246
  end
180
247
  { 'value' => value }
181
248
  end
@@ -219,11 +286,34 @@ module Oso
219
286
  when 'Call'
220
287
  Predicate.new(value['name'], args: value['args'].map { |a| to_ruby(a) })
221
288
  when 'Variable'
222
- Variable.new(value['name'])
289
+ Variable.new(value)
290
+ when 'Expression'
291
+ raise UnexpectedPolarTypeError, tag unless accept_expression
292
+
293
+ args = value['args'].map { |a| to_ruby(a) }
294
+ Expression.new(value['operator'], args)
295
+ when 'Pattern'
296
+ case value.keys.first
297
+ when 'Instance'
298
+ tag = value.values.first['tag']
299
+ fields = value.values.first['fields']['fields'].transform_values { |v| to_ruby(v) }
300
+ Pattern.new(tag, fields)
301
+ when 'Dictionary'
302
+ fields = value.values.first['fields'].transform_values { |v| to_ruby(v) }
303
+ Pattern.new(nil, fields)
304
+ else
305
+ raise UnexpectedPolarTypeError, "#{value.keys.first} variant of Pattern"
306
+ end
223
307
  else
224
308
  raise UnexpectedPolarTypeError, tag
225
309
  end
226
310
  end
311
+
312
+ def enrich_message(msg)
313
+ msg.gsub(/\^\{id: ([0-9]+)\}/) do
314
+ get_instance(Regexp.last_match[1].to_i).to_s
315
+ end
316
+ end
227
317
  end
228
318
  end
229
319
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Oso
4
+ module Polar
5
+ # Polar pattern.
6
+ class Pattern
7
+ attr_reader :tag, :fields
8
+
9
+ # @param tag [String]
10
+ # @param fields [Hash<String, Object>]
11
+ def initialize(tag, fields)
12
+ @tag = tag
13
+ @fields = fields
14
+ end
15
+
16
+ # @param other [Pattern]
17
+ # @return [Boolean]
18
+ def ==(other)
19
+ tag == other.tag && fields == other.fields
20
+ end
21
+
22
+ # @see #==
23
+ alias eql? ==
24
+ end
25
+ end
26
+ end
@@ -34,9 +34,11 @@ module Oso
34
34
  # @return [Host]
35
35
  attr_reader :host
36
36
 
37
- def initialize
37
+ def initialize # rubocop:disable Metrics/MethodLength
38
38
  @ffi_polar = FFI::Polar.create
39
39
  @host = Host.new(ffi_polar)
40
+ @ffi_polar.enrich_message = @host.method(:enrich_message)
41
+ @polar_roles_enabled = false
40
42
 
41
43
  # Register global constants.
42
44
  register_constant nil, name: 'nil'
@@ -50,11 +52,48 @@ module Oso
50
52
  register_class String
51
53
  end
52
54
 
55
+ def enable_roles # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
56
+ return if polar_roles_enabled
57
+
58
+ roles_helper = Class.new do
59
+ def self.join(separator, left, right)
60
+ [left, right].join(separator)
61
+ end
62
+ end
63
+ register_constant(roles_helper, name: '__oso_internal_roles_helpers__')
64
+ ffi_polar.enable_roles
65
+ self.polar_roles_enabled = true
66
+
67
+ # validate config
68
+ validation_query_results = []
69
+ loop do
70
+ query = ffi_polar.next_inline_query
71
+ break if query.nil?
72
+
73
+ new_host = host.dup
74
+ new_host.accept_expression = true
75
+ results = Query.new(query, host: new_host).to_a
76
+ raise InlineQueryFailedError, query.source if results.empty?
77
+
78
+ validation_query_results.push results
79
+ end
80
+
81
+ # turn bindings back into polar
82
+ validation_query_results = validation_query_results.map do |results|
83
+ results.map do |result|
84
+ { 'bindings' => result.transform_values { |v| host.to_polar(v) } }
85
+ end
86
+ end
87
+
88
+ ffi_polar.validate_roles_config(validation_query_results)
89
+ end
90
+
53
91
  # Clear all rules and rule sources from the current Polar instance
54
92
  #
55
93
  # @return [self] for chaining.
56
94
  def clear_rules
57
95
  ffi_polar.clear_rules
96
+ ffi_polar.enable_roles if polar_roles_enabled
58
97
  self
59
98
  end
60
99
 
@@ -81,7 +120,7 @@ module Oso
81
120
  # @raise [InlineQueryFailedError] on the first failed inline query.
82
121
  # @raise [Error] if any of the FFI calls raise one.
83
122
  # @return [self] for chaining.
84
- def load_str(str, filename: nil)
123
+ def load_str(str, filename: nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
85
124
  raise NullByteInPolarFileError if str.chomp("\0").include?("\0")
86
125
 
87
126
  ffi_polar.load(str, filename: filename)
@@ -91,6 +130,13 @@ module Oso
91
130
 
92
131
  raise InlineQueryFailedError, next_query.source if Query.new(next_query, host: host).first.nil?
93
132
  end
133
+
134
+ # If roles are enabled, re-validate config when new rules are loaded.
135
+ if polar_roles_enabled
136
+ self.polar_roles_enabled = false
137
+ enable_roles
138
+ end
139
+
94
140
  self
95
141
  end
96
142
 
@@ -170,6 +216,7 @@ module Oso
170
216
 
171
217
  # @return [FFI::Polar]
172
218
  attr_reader :ffi_polar
219
+ attr_accessor :polar_roles_enabled
173
220
 
174
221
  # The R and L in REPL for systems where readline is available.
175
222
  def repl_readline(prompt)
@@ -13,6 +13,7 @@ module Oso
13
13
  def initialize(ffi_query, host:)
14
14
  @calls = {}
15
15
  @ffi_query = ffi_query
16
+ ffi_query.enrich_message = host.method(:enrich_message)
16
17
  @host = host
17
18
  end
18
19
 
@@ -146,13 +147,12 @@ module Oso
146
147
  class_tag = event.data['class_tag']
147
148
  answer = host.isa?(instance, class_tag: class_tag)
148
149
  question_result(answer, call_id: event.data['call_id'])
149
- when 'ExternalUnify'
150
- left_instance_id = event.data['left_instance_id']
151
- right_instance_id = event.data['right_instance_id']
152
- answer = host.unify?(left_instance_id, right_instance_id)
153
- question_result(answer, call_id: event.data['call_id'])
154
150
  when 'Debug'
155
- puts event.data['message'] if event.data['message']
151
+ msg = event.data['message']
152
+ if msg
153
+ msg = host.enrich_message(msg) if msg
154
+ puts msg
155
+ end
156
156
  print 'debug> '
157
157
  begin
158
158
  input = $stdin.readline.chomp.chomp(';')
@@ -162,7 +162,10 @@ module Oso
162
162
  command = JSON.dump(host.to_polar(input))
163
163
  ffi_query.debug_command(command)
164
164
  when 'ExternalOp'
165
- raise UnimplementedOperationError, 'comparison operators'
165
+ op = event.data['operator']
166
+ args = event.data['args'].map(&host.method(:to_ruby))
167
+ answer = host.operator(op, args)
168
+ question_result(answer, call_id: event.data['call_id'])
166
169
  when 'NextExternal'
167
170
  call_id = event.data['call_id']
168
171
  iterable = event.data['iterable']
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.13.1'
4
+ VERSION = '0.15.0'
5
5
  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.13.1
4
+ version: 0.15.0
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-06-30 00:00:00.000000000 Z
11
+ date: 2021-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -133,6 +133,7 @@ files:
133
133
  - lib/oso/oso.rb
134
134
  - lib/oso/polar.rb
135
135
  - lib/oso/polar/errors.rb
136
+ - lib/oso/polar/expression.rb
136
137
  - lib/oso/polar/ffi.rb
137
138
  - lib/oso/polar/ffi/error.rb
138
139
  - lib/oso/polar/ffi/message.rb
@@ -141,6 +142,7 @@ files:
141
142
  - lib/oso/polar/ffi/query_event.rb
142
143
  - lib/oso/polar/ffi/source.rb
143
144
  - lib/oso/polar/host.rb
145
+ - lib/oso/polar/pattern.rb
144
146
  - lib/oso/polar/polar.rb
145
147
  - lib/oso/polar/predicate.rb
146
148
  - lib/oso/polar/query.rb