oso-oso 0.4.0 → 0.5.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: 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