oso-oso 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 919322c6735cfeb3615da5dd85b4188c59e8716d
4
- data.tar.gz: 1c881b365f4098e3bbdad3152168e82ab31df87f
3
+ metadata.gz: '09ee09c3366406a34633c870ddfe4ff271971529'
4
+ data.tar.gz: 764b0f3b440d567e19e45b71eff823b9000f35ef
5
5
  SHA512:
6
- metadata.gz: b898b3636cdb1cfa5fe909634292cf030bd60ccf768e964cb258c7e1c076d34b52e953bf8b37b27e2cc42926c44efdbd7b600dc21fea63e7144834870d80db1f
7
- data.tar.gz: 1e38944c4a55b81b3dbecbf1864be035632bd840e0f709380c8b14bfaecf657a1abb273b74b171aae35df9210beddb7e42f4c3547af74e482d0db4b214ca987f
6
+ metadata.gz: fdae19c2528c46a85004241f0a8e59639123e416724239670cb9ff2ffc1172a87bb116b481d575e466c74f634213abdced263211f991721f1ff990e1e085d884
7
+ data.tar.gz: 309a196e2a0a3725e3bc1f45be8382ed85026dc7fc271662efaef8e207b0df2891f26da48ece702f1d9ce3e3731ea52c0a6cea30bf50af6acde54f08b44d79ec
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- oso-oso (0.4.0)
4
+ oso-oso (0.5.0)
5
5
  ffi (~> 1.0)
6
6
 
7
7
  GEM
@@ -49,19 +49,19 @@ GEM
49
49
  diff-lcs (>= 1.2.0, < 2.0)
50
50
  rspec-support (~> 3.9.0)
51
51
  rspec-support (3.9.3)
52
- rubocop (0.89.0)
52
+ rubocop (0.89.1)
53
53
  parallel (~> 1.10)
54
54
  parser (>= 2.7.1.1)
55
55
  rainbow (>= 2.2.2, < 4.0)
56
56
  regexp_parser (>= 1.7)
57
57
  rexml
58
- rubocop-ast (>= 0.1.0, < 1.0)
58
+ rubocop-ast (>= 0.3.0, < 1.0)
59
59
  ruby-progressbar (~> 1.7)
60
60
  unicode-display_width (>= 1.4.0, < 2.0)
61
61
  rubocop-ast (0.3.0)
62
62
  parser (>= 2.7.1.4)
63
63
  ruby-progressbar (1.10.1)
64
- solargraph (0.39.13)
64
+ solargraph (0.39.14)
65
65
  backport (~> 1.1)
66
66
  benchmark
67
67
  bundler (>= 1.17.2)
@@ -88,8 +88,8 @@ DEPENDENCIES
88
88
  pry-byebug (~> 3.9.0)
89
89
  rake (~> 12.0)
90
90
  rspec (~> 3.0)
91
- rubocop (~> 0.89.0)
92
- solargraph (~> 0.39.8)
91
+ rubocop (~> 0.89.1)
92
+ solargraph (~> 0.39.14)
93
93
  yard (~> 0.9.25)
94
94
 
95
95
  BUNDLED WITH
data/bin/oso CHANGED
@@ -4,4 +4,4 @@
4
4
  require 'bundler/setup'
5
5
  require 'oso'
6
6
 
7
- Oso.new.repl(load: true)
7
+ Oso.new.repl(ARGV)
Binary file
Binary file
@@ -3,7 +3,7 @@
3
3
  require_relative 'polar/polar'
4
4
 
5
5
  module Oso
6
- # Oso authorization API.
6
+ # oso authorization API.
7
7
  class Oso < Polar::Polar
8
8
  def initialize
9
9
  super
@@ -11,6 +11,13 @@ module Oso
11
11
  register_class(PathMapper, name: 'PathMapper')
12
12
  end
13
13
 
14
+ # Query the knowledge base to determine whether an actor is allowed to
15
+ # perform an action upon a resource.
16
+ #
17
+ # @param actor [Object] Subject.
18
+ # @param action [Object] Verb.
19
+ # @param resource [Object] Object.
20
+ # @return [Boolean] An access control decision.
14
21
  def allowed?(actor:, action:, resource:)
15
22
  query_rule('allow', actor, action, resource).next
16
23
  true
@@ -37,7 +37,13 @@ module Oso
37
37
  # Wrapper class for Error FFI pointer + operations.
38
38
  class Error < ::FFI::AutoPointer
39
39
  def self.release(ptr)
40
- Rust.free(ptr)
40
+ Rust.free(ptr) unless ptr.null?
41
+ end
42
+ end
43
+ # Wrapper class for Message FFI pointer + operations.
44
+ class Message < ::FFI::AutoPointer
45
+ def self.release(ptr)
46
+ Rust.free(ptr) unless ptr.null?
41
47
  end
42
48
  end
43
49
  end
@@ -49,3 +55,4 @@ require 'oso/polar/ffi/polar'
49
55
  require 'oso/polar/ffi/query'
50
56
  require 'oso/polar/ffi/query_event'
51
57
  require 'oso/polar/ffi/error'
58
+ require 'oso/polar/ffi/message'
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Oso
4
+ module Polar
5
+ module FFI
6
+ # Wrapper class for Message FFI pointer + operations.
7
+ class Message < ::FFI::AutoPointer
8
+ # @return [String]
9
+ def to_s
10
+ @to_s ||= read_string.force_encoding('UTF-8')
11
+ end
12
+
13
+ Rust = Module.new do
14
+ extend ::FFI::Library
15
+ ffi_lib FFI::LIB_PATH
16
+
17
+ attach_function :free, :string_free, [Message], :int32
18
+ end
19
+
20
+ def process
21
+ message = JSON.parse(to_s)
22
+ kind = message['kind']
23
+ msg = message['msg']
24
+
25
+ case kind
26
+ when 'Print'
27
+ puts(msg)
28
+ when 'Warning'
29
+ warn('[warning] %<msg>s')
30
+ end
31
+ end
32
+
33
+ private_constant :Rust
34
+ end
35
+ end
36
+ end
37
+ end
@@ -16,6 +16,7 @@ module Oso
16
16
  attach_function :new_query_from_str, :polar_new_query, [FFI::Polar, :string, :uint32], FFI::Query
17
17
  attach_function :new_query_from_term, :polar_new_query_from_term, [FFI::Polar, :string, :uint32], FFI::Query
18
18
  attach_function :register_constant, :polar_register_constant, [FFI::Polar, :string, :string], :int32
19
+ attach_function :next_message, :polar_next_polar_message, [FFI::Polar], FFI::Message
19
20
  attach_function :free, :polar_free, [FFI::Polar], :int32
20
21
  end
21
22
  private_constant :Rust
@@ -33,7 +34,9 @@ module Oso
33
34
  # @param filename [String]
34
35
  # @raise [FFI::Error] if the FFI call returns an error.
35
36
  def load_str(src, filename: nil)
36
- raise FFI::Error.get if Rust.load_str(self, src, filename).zero?
37
+ loaded = Rust.load_str(self, src, filename)
38
+ process_messages
39
+ raise FFI::Error.get if loaded.zero?
37
40
  end
38
41
 
39
42
  # @return [FFI::Query] if there are remaining inline queries.
@@ -41,6 +44,7 @@ module Oso
41
44
  # @raise [FFI::Error] if the FFI call returns an error.
42
45
  def next_inline_query
43
46
  query = Rust.next_inline_query(self, 0)
47
+ process_messages
44
48
  query.null? ? nil : query
45
49
  end
46
50
 
@@ -60,6 +64,7 @@ module Oso
60
64
  # @raise [FFI::Error] if the FFI call returns an error.
61
65
  def new_query_from_str(str)
62
66
  query = Rust.new_query_from_str(self, str, 0)
67
+ process_messages
63
68
  raise FFI::Error.get if query.null?
64
69
 
65
70
  query
@@ -70,6 +75,7 @@ module Oso
70
75
  # @raise [FFI::Error] if the FFI call returns an error.
71
76
  def new_query_from_term(term)
72
77
  query = Rust.new_query_from_term(self, JSON.dump(term), 0)
78
+ process_messages
73
79
  raise FFI::Error.get if query.null?
74
80
 
75
81
  query
@@ -79,7 +85,21 @@ module Oso
79
85
  # @param value [Hash<String, Object>]
80
86
  # @raise [FFI::Error] if the FFI call returns an error.
81
87
  def register_constant(name, value:)
82
- raise FFI::Error.get if Rust.register_constant(self, name, JSON.dump(value)).zero?
88
+ registered = Rust.register_constant(self, name, JSON.dump(value))
89
+ raise FFI::Error.get if registered.zero?
90
+ end
91
+
92
+ def next_message
93
+ Rust.next_message(self)
94
+ end
95
+
96
+ def process_messages
97
+ loop do
98
+ message = next_message
99
+ break if message.null?
100
+
101
+ message.process
102
+ end
83
103
  end
84
104
  end
85
105
  end
@@ -14,6 +14,7 @@ module Oso
14
14
  attach_function :question_result, :polar_question_result, [FFI::Query, :uint64, :int32], :int32
15
15
  attach_function :application_error, :polar_application_error, [FFI::Query, :string], :int32
16
16
  attach_function :next_event, :polar_next_query_event, [FFI::Query], FFI::QueryEvent
17
+ attach_function :next_message, :polar_next_query_message, [FFI::Query], FFI::Message
17
18
  attach_function :free, :query_free, [FFI::Query], :int32
18
19
  end
19
20
  private_constant :Rust
@@ -22,6 +23,7 @@ module Oso
22
23
  # @raise [FFI::Error] if the FFI call returns an error.
23
24
  def debug_command(cmd)
24
25
  res = Rust.debug_command(self, cmd)
26
+ process_messages
25
27
  raise FFI::Error.get if res.zero?
26
28
  end
27
29
 
@@ -54,10 +56,24 @@ module Oso
54
56
  # @raise [FFI::Error] if the FFI call returns an error.
55
57
  def next_event
56
58
  event = Rust.next_event(self)
59
+ process_messages
57
60
  raise FFI::Error.get if event.null?
58
61
 
59
62
  ::Oso::Polar::QueryEvent.new(JSON.parse(event.to_s))
60
63
  end
64
+
65
+ def next_message
66
+ Rust.next_message(self)
67
+ end
68
+
69
+ def process_messages
70
+ loop do
71
+ message = next_message
72
+ break if message.null?
73
+
74
+ message.process
75
+ end
76
+ end
61
77
  end
62
78
  end
63
79
  end
@@ -46,6 +46,7 @@ module Oso
46
46
  #
47
47
  # @param cls [Class] the class to cache
48
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.
49
50
  # @return [String] the name the class is cached as.
50
51
  # @raise [UnregisteredClassError] if the class has not been registered.
51
52
  def cache_class(cls, name:, constructor:) # rubocop:disable Metrics/MethodLength
@@ -75,7 +76,7 @@ module Oso
75
76
  constructors[name]
76
77
  end
77
78
 
78
- # Check if an instance has been cached.
79
+ # Check if an instance exists in the {#instances} cache.
79
80
  #
80
81
  # @param id [Integer]
81
82
  # @return [Boolean]
@@ -99,7 +100,8 @@ module Oso
99
100
  instances[id]
100
101
  end
101
102
 
102
- # Cache a Ruby instance, fetching a {#new_id} if one isn't provided.
103
+ # Cache a Ruby instance in the {#instances} cache, fetching a {#new_id}
104
+ # if one isn't provided.
103
105
  #
104
106
  # @param instance [Object]
105
107
  # @param id [Integer]
@@ -12,6 +12,26 @@ class TrueClass; include PolarBoolean; end
12
12
  # Monkey-patch Ruby false type.
13
13
  class FalseClass; include PolarBoolean; end
14
14
 
15
+ # https://github.com/ruby/ruby/blob/bb9ecd026a6cadd5d0f85ac061649216806ed935/lib/bundler/vendor/thor/lib/thor/shell/color.rb#L99-L105
16
+ def supports_color
17
+ $stdout.tty? && $stderr.tty? && ENV['NO_COLOR'].nil?
18
+ end
19
+
20
+ if supports_color
21
+ RESET = "\x1b[0m"
22
+ FG_BLUE = "\x1b[34m"
23
+ FG_RED = "\x1b[31m"
24
+ else
25
+ RESET = ''
26
+ FG_BLUE = ''
27
+ FG_RED = ''
28
+ end
29
+
30
+ def print_error(error)
31
+ warn FG_RED + error.class.name.split('::').last + RESET
32
+ warn error.message
33
+ end
34
+
15
35
  module Oso
16
36
  module Polar
17
37
  # Create and manage an instance of the Polar runtime.
@@ -41,7 +61,7 @@ module Oso
41
61
  @ffi_polar = FFI::Polar.create
42
62
  end
43
63
 
44
- # Enqueue a Polar policy file for loading into the KB.
64
+ # Load a Polar policy file.
45
65
  #
46
66
  # @param name [String]
47
67
  # @raise [PolarFileExtensionError] if provided filename has invalid extension.
@@ -90,7 +110,7 @@ module Oso
90
110
  end
91
111
  end
92
112
 
93
- # Query for a predicate, parsing it if necessary.
113
+ # Query for a Polar predicate or string.
94
114
  #
95
115
  # @overload query(query)
96
116
  # @param query [String]
@@ -123,48 +143,6 @@ module Oso
123
143
  query(Predicate.new(name, args: args))
124
144
  end
125
145
 
126
- # Start a REPL session.
127
- #
128
- # @raise [Error] if the FFI call raises one.
129
- def repl(load: false) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
130
- ARGV.map { |f| load_file(f) } if load
131
-
132
- loop do # rubocop:disable Metrics/BlockLength
133
- print 'query> '
134
- begin
135
- query = $stdin.readline.chomp.chomp(';')
136
- rescue EOFError
137
- return
138
- end
139
-
140
- begin
141
- ffi_query = ffi_polar.new_query_from_str(query)
142
- rescue ParseError => e
143
- puts "Parse error: #{e}"
144
- next
145
- end
146
-
147
- begin
148
- results = Query.new(ffi_query, host: host).results.to_a
149
- rescue PolarRuntimeError => e
150
- puts e
151
- next
152
- end
153
-
154
- if results.empty?
155
- pp false
156
- else
157
- results.each do |result|
158
- if result.empty?
159
- pp true
160
- else
161
- pp result
162
- end
163
- end
164
- end
165
- end
166
- end
167
-
168
146
  # Register a Ruby class with Polar.
169
147
  #
170
148
  # @param cls [Class]
@@ -181,6 +159,21 @@ module Oso
181
159
  ffi_polar.register_constant(name, value: host.to_polar_term(value))
182
160
  end
183
161
 
162
+ # Start a REPL session.
163
+ #
164
+ # @param files [Array<String>]
165
+ # @raise [Error] if the FFI call raises one.
166
+ def repl(files = [])
167
+ files.map { |f| load_file(f) }
168
+ prompt = "#{FG_BLUE}query>#{RESET} "
169
+ # Try loading the readline module from the Ruby stdlib. If we get a
170
+ # LoadError, fall back to the standard REPL with no readline support.
171
+ require 'readline'
172
+ repl_readline(prompt)
173
+ rescue LoadError
174
+ repl_standard(prompt)
175
+ end
176
+
184
177
  private
185
178
 
186
179
  # @return [FFI::Polar]
@@ -189,6 +182,66 @@ module Oso
189
182
  attr_reader :loaded_names
190
183
  # @return [Hash<String, String>]
191
184
  attr_reader :loaded_contents
185
+
186
+ # The R and L in REPL for systems where readline is available.
187
+ def repl_readline(prompt)
188
+ while (buf = Readline.readline(prompt, true))
189
+ if /^\s*$/ =~ buf # Don't add empty entries to history.
190
+ Readline::HISTORY.pop
191
+ next
192
+ end
193
+ process_line(buf)
194
+ end
195
+ rescue Interrupt # rubocop:disable Lint/SuppressedException
196
+ end
197
+
198
+ # The R and L in REPL for systems where readline is not available.
199
+ def repl_standard(prompt)
200
+ loop do
201
+ puts prompt
202
+ begin
203
+ buf = $stdin.readline
204
+ rescue EOFError
205
+ return
206
+ end
207
+ process_line(buf)
208
+ end
209
+ rescue Interrupt # rubocop:disable Lint/SuppressedException
210
+ end
211
+
212
+ # Process a line of user input in the REPL.
213
+ #
214
+ # @param buf [String]
215
+ def process_line(buf) # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/AbcSize
216
+ query = buf.chomp.chomp(';')
217
+ begin
218
+ ffi_query = ffi_polar.new_query_from_str(query)
219
+ rescue ParseError => e
220
+ print_error(e)
221
+ return
222
+ end
223
+
224
+ begin
225
+ results = Query.new(ffi_query, host: host).results.to_a
226
+ rescue PolarRuntimeError => e
227
+ print_error(e)
228
+ return
229
+ end
230
+
231
+ if results.empty?
232
+ puts false
233
+ else
234
+ results.each do |result|
235
+ if result.empty?
236
+ puts true
237
+ else
238
+ result.each do |variable, value|
239
+ puts "#{variable} => #{value.inspect}"
240
+ end
241
+ end
242
+ end
243
+ end
244
+ end
192
245
  end
193
246
  end
194
247
  end
@@ -43,12 +43,16 @@ module Oso
43
43
  # @param args [Array<Hash>]
44
44
  # @raise [InvalidCallError] if the method doesn't exist on the instance or
45
45
  # the args passed to the method are invalid.
46
- def register_call(method, call_id:, instance:, args:)
46
+ def register_call(attribute, call_id:, instance:, args:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
47
47
  return if calls.key?(call_id)
48
48
 
49
- args = args.map { |a| host.to_ruby(a) }
50
49
  instance = host.to_ruby(instance)
51
- result = instance.__send__(method, *args)
50
+ if args.nil?
51
+ result = instance.__send__(attribute)
52
+ else
53
+ args = args.map { |a| host.to_ruby(a) }
54
+ result = instance.__send__(attribute, *args)
55
+ end
52
56
  result = [result].to_enum unless result.is_a? Enumerator # Call must be a generator.
53
57
  calls[call_id] = result.lazy
54
58
  rescue ArgumentError, NoMethodError
@@ -89,8 +93,8 @@ module Oso
89
93
  # @param call_id [Integer]
90
94
  # @param instance [Hash<String, Object>]
91
95
  # @raise [Error] if the FFI call raises one.
92
- def handle_call(method, call_id:, instance:, args:)
93
- register_call(method, call_id: call_id, instance: instance, args: args)
96
+ def handle_call(attribute, call_id:, instance:, args:)
97
+ register_call(attribute, call_id: call_id, instance: instance, args: args)
94
98
  result = JSON.dump(next_call_result(call_id))
95
99
  call_result(result, call_id: call_id)
96
100
  rescue InvalidCallError => e
@@ -133,9 +137,9 @@ module Oso
133
137
  when 'ExternalCall'
134
138
  call_id = event.data['call_id']
135
139
  instance = event.data['instance']
136
- method = event.data['attribute']
140
+ attribute = event.data['attribute']
137
141
  args = event.data['args']
138
- handle_call(method, call_id: call_id, instance: instance, args: args)
142
+ handle_call(attribute, call_id: call_id, instance: instance, args: args)
139
143
  when 'ExternalIsSubSpecializer'
140
144
  instance_id = event.data['instance_id']
141
145
  left_tag = event.data['left_class_tag']
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Oso
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.add_development_dependency 'pry-byebug', '~> 3.9.0'
35
35
  spec.add_development_dependency 'rake', '~> 12.0'
36
36
  spec.add_development_dependency 'rspec', '~> 3.0'
37
- spec.add_development_dependency 'rubocop', '~> 0.89.0'
38
- spec.add_development_dependency 'solargraph', '~> 0.39.8'
37
+ spec.add_development_dependency 'rubocop', '~> 0.89.1'
38
+ spec.add_development_dependency 'solargraph', '~> 0.39.14'
39
39
  spec.add_development_dependency 'yard', '~> 0.9.25'
40
40
  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.0
4
+ version: 0.5.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: 2020-08-11 00:00:00.000000000 Z
11
+ date: 2020-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -72,28 +72,28 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 0.89.0
75
+ version: 0.89.1
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 0.89.0
82
+ version: 0.89.1
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: solargraph
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 0.39.8
89
+ version: 0.39.14
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 0.39.8
96
+ version: 0.39.14
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: yard
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -137,6 +137,7 @@ files:
137
137
  - lib/oso/polar/errors.rb
138
138
  - lib/oso/polar/ffi.rb
139
139
  - lib/oso/polar/ffi/error.rb
140
+ - lib/oso/polar/ffi/message.rb
140
141
  - lib/oso/polar/ffi/polar.rb
141
142
  - lib/oso/polar/ffi/query.rb
142
143
  - lib/oso/polar/ffi/query_event.rb