oso-oso 0.6.0 → 0.8.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 70321ccb3dc8e6468b89109aa7d9bbb97868d1be
4
- data.tar.gz: 8b7c1873eba4dae6d1150f8f91a74a3d5a72e51c
3
+ metadata.gz: 8d51e82a017eaa54ea9a33b9199461015e3eac36
4
+ data.tar.gz: 6903b31bf79562138d28d96c1c33d51270f65ba5
5
5
  SHA512:
6
- metadata.gz: 639ea45ef8a86ebf97ae20ee1977499e18f04827b05d869369c7ec01792eb7a7747c6bae9f7d7789af4993a4b04edef5b375c9caa504c6e09afe701ed931874f
7
- data.tar.gz: 7dc68ed639125472e808ff375059ceb6d70837fad20c05672a183452bef78eeee56dcc0dc58579d21536749bc8e2fbc2628baa349473984c824d23b38d1270f4
6
+ metadata.gz: 91cc12a5af5fc9b16492681e398b8d09d341ac4dd4af34ca0b216c774248024723844937b1c106e7aceba03dc574b6377530cb74f2e591c86cab62ae836538df
7
+ data.tar.gz: 1cd76a3ba326f3e505e956f22f41ae1a572bc995e990be19e294dae803bd0683f01b7d3589ca4a44923848deb150ccb401ee77bfaf99faacc6e9b032a61b0fe7
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- oso-oso (0.6.0)
4
+ oso-oso (0.8.2)
5
5
  ffi (~> 1.0)
6
6
 
7
7
  GEM
Binary file
Binary file
@@ -32,13 +32,13 @@ module Oso
32
32
  # Errors originating from this side of the FFI boundary.
33
33
 
34
34
  class UnregisteredClassError < PolarRuntimeError; end
35
- class MissingConstructorError < PolarRuntimeError; end
36
35
  class UnregisteredInstanceError < PolarRuntimeError; end
37
36
  class DuplicateInstanceRegistrationError < PolarRuntimeError; end
38
37
 
39
38
  # TODO: I think this should probably have some arguments to say what the call is
40
39
  class InvalidCallError < PolarRuntimeError; end
41
40
  class InvalidConstructorError < PolarRuntimeError; end
41
+ class InvalidIteratorError < PolarRuntimeError; end
42
42
  class InvalidQueryTypeError < PolarRuntimeError; end
43
43
  class NullByteInPolarFileError < PolarRuntimeError; end
44
44
  class UnexpectedPolarTypeError < PolarRuntimeError; end
@@ -68,6 +68,12 @@ module Oso
68
68
  end
69
69
  end
70
70
 
71
+ class UnimplementedOperationError < PolarRuntimeError # rubocop:disable Style/Documentation
72
+ def initialize(operation)
73
+ super("#{operation} are unimplemented in the oso Ruby library")
74
+ end
75
+ end
76
+
71
77
  # Generic operational exception.
72
78
  class OperationalError < Error; end
73
79
  class UnknownError < OperationalError; end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+
3
5
  module Oso
4
6
  module Polar
5
7
  module FFI
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+
3
5
  module Oso
4
6
  module Polar
5
7
  module FFI
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+
3
5
  module Oso
4
6
  module Polar
5
7
  module FFI
@@ -11,6 +13,7 @@ module Oso
11
13
 
12
14
  attach_function :new, :polar_new, [], FFI::Polar
13
15
  attach_function :load, :polar_load, [FFI::Polar, :string, :string], :int32
16
+ attach_function :clear_rules, :polar_clear_rules, [FFI::Polar], :int32
14
17
  attach_function :next_inline_query, :polar_next_inline_query, [FFI::Polar, :uint32], FFI::Query
15
18
  attach_function :new_id, :polar_get_external_id, [FFI::Polar], :uint64
16
19
  attach_function :new_query_from_str, :polar_new_query, [FFI::Polar, :string, :uint32], FFI::Query
@@ -39,6 +42,13 @@ module Oso
39
42
  raise FFI::Error.get if loaded.zero?
40
43
  end
41
44
 
45
+ # @raise [FFI::Error] if the FFI call returns an error.
46
+ def clear_rules
47
+ cleared = Rust.clear_rules(self)
48
+ process_messages
49
+ raise FFI::Error.get if cleared.zero?
50
+ end
51
+
42
52
  # @return [FFI::Query] if there are remaining inline queries.
43
53
  # @return [nil] if there are no remaining inline queries.
44
54
  # @raise [FFI::Error] if the FFI call returns an error.
@@ -84,7 +94,7 @@ module Oso
84
94
  # @param name [String]
85
95
  # @param value [Hash<String, Object>]
86
96
  # @raise [FFI::Error] if the FFI call returns an error.
87
- def register_constant(name, value:)
97
+ def register_constant(value, name:)
88
98
  registered = Rust.register_constant(self, name, JSON.dump(value))
89
99
  raise FFI::Error.get if registered.zero?
90
100
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+
3
5
  module Oso
4
6
  module Polar
5
7
  module FFI
@@ -10,8 +10,6 @@ module Oso
10
10
  attr_reader :ffi_polar
11
11
  # @return [Hash<String, Class>]
12
12
  attr_reader :classes
13
- # @return [Hash<String, Object>]
14
- attr_reader :constructors
15
13
  # @return [Hash<Integer, Object>]
16
14
  attr_reader :instances
17
15
 
@@ -20,14 +18,12 @@ module Oso
20
18
  def initialize(ffi_polar)
21
19
  @ffi_polar = ffi_polar
22
20
  @classes = {}
23
- @constructors = {}
24
21
  @instances = {}
25
22
  end
26
23
 
27
24
  def initialize_copy(other)
28
25
  @ffi_polar = other.ffi_polar
29
26
  @classes = other.classes.dup
30
- @constructors = other.constructors.dup
31
27
  @instances = other.instances.dup
32
28
  end
33
29
 
@@ -44,38 +40,18 @@ module Oso
44
40
 
45
41
  # Store a Ruby class in the {#classes} cache.
46
42
  #
47
- # @param cls [Class] the class to cache
48
- # @param name [String] the name to cache the class as. Defaults to the name of the class.
49
- # @param constructor [Proc] optional custom constructor function. Defaults to the :new method.
43
+ # @param cls [Class] the class to cache.
44
+ # @param name [String] the name to cache the class as.
50
45
  # @return [String] the name the class is cached as.
51
- # @raise [UnregisteredClassError] if the class has not been registered.
52
- def cache_class(cls, name:, constructor:) # rubocop:disable Metrics/MethodLength
53
- name = cls.name if name.nil?
46
+ # @raise [DuplicateClassAliasError] if attempting to register a class
47
+ # under a previously-registered name.
48
+ def cache_class(cls, name:)
54
49
  raise DuplicateClassAliasError, name: name, old: get_class(name), new: cls if classes.key? name
55
50
 
56
51
  classes[name] = cls
57
- if constructor.nil?
58
- constructors[name] = :new
59
- elsif constructor.respond_to? :call
60
- constructors[name] = constructor
61
- else
62
- raise InvalidConstructorError
63
- end
64
52
  name
65
53
  end
66
54
 
67
- # Fetch a constructor from the {#constructors} cache.
68
- #
69
- # @param name [String]
70
- # @return [Symbol] if constructor is the default of `:new`.
71
- # @return [Proc] if a custom constructor was registered.
72
- # @raise [MissingConstructorError] if the constructor has not been registered.
73
- def get_constructor(name)
74
- raise MissingConstructorError, name unless constructors.key? name
75
-
76
- constructors[name]
77
- end
78
-
79
55
  # Check if an instance exists in the {#instances} cache.
80
56
  #
81
57
  # @param id [Integer]
@@ -100,12 +76,12 @@ module Oso
100
76
  instances[id]
101
77
  end
102
78
 
103
- # Cache a Ruby instance in the {#instances} cache, fetching a {#new_id}
104
- # if one isn't provided.
79
+ # Cache a Ruby instance in the {#instances} cache, fetching a new id if
80
+ # one isn't provided.
105
81
  #
106
82
  # @param instance [Object]
107
- # @param id [Integer]
108
- # @return [Integer]
83
+ # @param id [Integer] the instance ID. Generated via FFI if not provided.
84
+ # @return [Integer] the instance ID.
109
85
  def cache_instance(instance, id: nil)
110
86
  id = ffi_polar.new_id if id.nil?
111
87
  instances[id] = instance
@@ -114,24 +90,17 @@ module Oso
114
90
 
115
91
  # Construct and cache a Ruby instance.
116
92
  #
117
- # @param cls_name [String]
118
- # @param args [Array<Object>]
119
- # @param kwargs [Hash<String, Object>]
120
- # @param id [Integer]
93
+ # @param cls_name [String] name of the instance's class.
94
+ # @param args [Array<Object>] positional args to the constructor.
95
+ # @param kwargs [Hash<String, Object>] keyword args to the constructor.
96
+ # @param id [Integer] the instance ID.
121
97
  # @raise [PolarRuntimeError] if instance construction fails.
122
- def make_instance(cls_name, args:, kwargs:, id:) # rubocop:disable Metrics/MethodLength
123
- constructor = get_constructor(cls_name)
124
- # The kwargs.empty? checks are for Ruby < 2.7.
125
- instance = if constructor == :new
126
- if kwargs.empty?
127
- get_class(cls_name).__send__(:new, *args)
128
- else
129
- get_class(cls_name).__send__(:new, *args, **kwargs)
130
- end
131
- elsif kwargs.empty?
132
- constructor.call(*args)
98
+ # @return [Integer] the instance ID.
99
+ def make_instance(cls_name, args:, kwargs:, id:)
100
+ instance = if kwargs.empty? # This check is for Ruby < 2.7.
101
+ get_class(cls_name).__send__(:new, *args)
133
102
  else
134
- constructor.call(*args, **kwargs)
103
+ get_class(cls_name).__send__(:new, *args, **kwargs)
135
104
  end
136
105
  cache_instance(instance, id: id)
137
106
  rescue StandardError => e
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
- require 'pp'
5
- require 'set'
6
- require 'digest/md5'
7
-
8
3
  # Missing Ruby type.
9
4
  module PolarBoolean; end
10
5
  # Monkey-patch Ruby true type.
@@ -43,6 +38,9 @@ module Oso
43
38
  @ffi_polar = FFI::Polar.create
44
39
  @host = Host.new(ffi_polar)
45
40
 
41
+ # Register global constants.
42
+ register_constant nil, name: 'nil'
43
+
46
44
  # Register built-in classes.
47
45
  register_class PolarBoolean, name: 'Boolean'
48
46
  register_class Integer
@@ -52,9 +50,12 @@ module Oso
52
50
  register_class String
53
51
  end
54
52
 
55
- # Replace the current Polar instance but retain all registered classes and constructors.
56
- def clear
57
- @ffi_polar = FFI::Polar.create
53
+ # Clear all rules and rule sources from the current Polar instance
54
+ #
55
+ # @return [self] for chaining.
56
+ def clear_rules
57
+ ffi_polar.clear_rules
58
+ self
58
59
  end
59
60
 
60
61
  # Load a Polar policy file.
@@ -62,6 +63,7 @@ module Oso
62
63
  # @param name [String]
63
64
  # @raise [PolarFileExtensionError] if provided filename has invalid extension.
64
65
  # @raise [PolarFileNotFoundError] if provided filename does not exist.
66
+ # @return [self] for chaining.
65
67
  def load_file(name)
66
68
  raise PolarFileExtensionError, name unless File.extname(name) == '.polar'
67
69
 
@@ -78,6 +80,7 @@ module Oso
78
80
  # @raise [NullByteInPolarFileError] if str includes a non-terminating null byte.
79
81
  # @raise [InlineQueryFailedError] on the first failed inline query.
80
82
  # @raise [Error] if any of the FFI calls raise one.
83
+ # @return [self] for chaining.
81
84
  def load_str(str, filename: nil) # rubocop:disable Metrics/MethodLength
82
85
  raise NullByteInPolarFileError if str.chomp("\0").include?("\0")
83
86
 
@@ -92,6 +95,7 @@ module Oso
92
95
  raise InlineQueryFailedError, next_query.source
93
96
  end
94
97
  end
98
+ self
95
99
  end
96
100
 
97
101
  # Query for a Polar predicate or string.
@@ -129,18 +133,26 @@ module Oso
129
133
 
130
134
  # Register a Ruby class with Polar.
131
135
  #
132
- # @param cls [Class]
133
- # @param name [String]
134
- # @param from_polar [Proc]
135
- # @raise [InvalidConstructorError] if provided an invalid 'from_polar' constructor.
136
- def register_class(cls, name: nil, from_polar: nil)
137
- from_polar = Proc.new if block_given?
138
- name = host.cache_class(cls, name: name, constructor: from_polar)
139
- register_constant(name, value: cls)
136
+ # @param cls [Class] the class to register.
137
+ # @param name [String] the name to register the class as. Defaults to the name of the class.
138
+ # @raise [DuplicateClassAliasError] if attempting to register a class
139
+ # under a previously-registered name.
140
+ # @raise [FFI::Error] if the FFI call returns an error.
141
+ # @return [self] for chaining.
142
+ def register_class(cls, name: nil)
143
+ name = host.cache_class(cls, name: name || cls.name)
144
+ register_constant(cls, name: name)
140
145
  end
141
146
 
142
- def register_constant(name, value:)
143
- ffi_polar.register_constant(name, value: host.to_polar(value))
147
+ # Register a Ruby object with Polar.
148
+ #
149
+ # @param value [Object] the object to register.
150
+ # @param name [String] the name to register the object as.
151
+ # @return [self] for chaining.
152
+ # @raise [FFI::Error] if the FFI call returns an error.
153
+ def register_constant(value, name:)
154
+ ffi_polar.register_constant(host.to_polar(value), name: name)
155
+ self
144
156
  end
145
157
 
146
158
  # Start a REPL session.
@@ -162,10 +174,6 @@ module Oso
162
174
 
163
175
  # @return [FFI::Polar]
164
176
  attr_reader :ffi_polar
165
- # @return [Hash<String, String>]
166
- attr_reader :loaded_names
167
- # @return [Hash<String, String>]
168
- attr_reader :loaded_contents
169
177
 
170
178
  # The R and L in REPL for systems where readline is available.
171
179
  def repl_readline(prompt)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+
3
5
  module Oso
4
6
  module Polar
5
7
  # A single Polar query.
@@ -34,33 +36,6 @@ module Oso
34
36
  ffi_query.question_result(result, call_id: call_id)
35
37
  end
36
38
 
37
- # Register a Ruby method call, wrapping the call result in a generator if
38
- # it isn't already one.
39
- #
40
- # @param method [#to_sym]
41
- # @param call_id [Integer]
42
- # @param instance [Hash<String, Object>]
43
- # @param args [Array<Hash>]
44
- # @raise [InvalidCallError] if the method doesn't exist on the instance or
45
- # the args passed to the method are invalid.
46
- def register_call(attribute, call_id:, instance:, args:, kwargs:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
47
- return if calls.key?(call_id)
48
-
49
- instance = host.to_ruby(instance)
50
- args = args.map { |a| host.to_ruby(a) }
51
- kwargs = Hash[kwargs.map { |k, v| [k.to_sym, host.to_ruby(v)] }]
52
- # The kwargs.empty? check is for Ruby < 2.7.
53
- result = if kwargs.empty?
54
- instance.__send__(attribute, *args)
55
- else
56
- instance.__send__(attribute, *args, **kwargs)
57
- end
58
- result = [result].to_enum unless result.is_a? Enumerator # Call must be a generator.
59
- calls[call_id] = result.lazy
60
- rescue ArgumentError, NoMethodError
61
- raise InvalidCallError
62
- end
63
-
64
39
  # Send next result of Ruby method call across FFI boundary.
65
40
  #
66
41
  # @param result [String]
@@ -95,13 +70,33 @@ module Oso
95
70
  # @param call_id [Integer]
96
71
  # @param instance [Hash<String, Object>]
97
72
  # @raise [Error] if the FFI call raises one.
98
- def handle_call(attribute, call_id:, instance:, args:, kwargs:)
99
- register_call(attribute, call_id: call_id, instance: instance, args: args, kwargs: kwargs)
100
- result = JSON.dump(next_call_result(call_id))
73
+ def handle_call(attribute, call_id:, instance:, args:, kwargs:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
74
+ instance = host.to_ruby(instance)
75
+ args = args.map { |a| host.to_ruby(a) }
76
+ kwargs = Hash[kwargs.map { |k, v| [k.to_sym, host.to_ruby(v)] }]
77
+ # The kwargs.empty? check is for Ruby < 2.7.
78
+ result = if kwargs.empty?
79
+ instance.__send__(attribute, *args)
80
+ else
81
+ instance.__send__(attribute, *args, **kwargs)
82
+ end
83
+ result = JSON.dump(host.to_polar(result))
101
84
  call_result(result, call_id: call_id)
102
- rescue InvalidCallError => e
85
+ rescue ArgumentError, NoMethodError => e
103
86
  application_error(e.message)
104
87
  call_result(nil, call_id: call_id)
88
+ end
89
+
90
+ def handle_next_external(call_id, iterable)
91
+ unless calls.key? call_id
92
+ value = host.to_ruby iterable
93
+ raise InvalidIteratorError unless value.is_a? Enumerable
94
+
95
+ calls[call_id] = value.lazy
96
+ end
97
+
98
+ result = JSON.dump(next_call_result(call_id))
99
+ call_result(result, call_id: call_id)
105
100
  rescue StopIteration
106
101
  call_result(nil, call_id: call_id)
107
102
  end
@@ -169,6 +164,12 @@ module Oso
169
164
  end
170
165
  command = JSON.dump(host.to_polar(input))
171
166
  ffi_query.debug_command(command)
167
+ when 'ExternalOp'
168
+ raise UnimplementedOperationError, 'comparison operators'
169
+ when 'NextExternal'
170
+ call_id = event.data['call_id']
171
+ iterable = event.data['iterable']
172
+ handle_next_external(call_id, iterable)
172
173
  else
173
174
  raise "Unhandled event: #{JSON.dump(event.inspect)}"
174
175
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Oso
4
- VERSION = '0.6.0'
4
+ VERSION = '0.8.2'
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.6.0
4
+ version: 0.8.2
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: 2020-09-22 00:00:00.000000000 Z
11
+ date: 2020-11-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi