oso-oso 0.2.5 → 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.
@@ -3,23 +3,61 @@
3
3
  module Oso
4
4
  module Polar
5
5
  # A single Polar query.
6
- class Query
6
+ class Query # rubocop:disable Metrics/ClassLength
7
+ # @return [Enumerator]
7
8
  attr_reader :results
8
9
 
9
- # @param ffi_instance [FFI::Query]
10
- # @param polar [Polar]
11
- def initialize(ffi_instance, polar:)
12
- @ffi_instance = ffi_instance
13
- @polar = polar
10
+ # @param ffi_query [FFI::Query]
11
+ # @param host [Oso::Polar::Host]
12
+ def initialize(ffi_query, host:)
13
+ @calls = {}
14
+ @ffi_query = ffi_query
15
+ @host = host
14
16
  @results = start
15
17
  end
16
18
 
17
19
  private
18
20
 
21
+ # @return [Hash<Integer, Enumerator>]
22
+ attr_reader :calls
19
23
  # @return [FFI::Query]
20
- attr_reader :ffi_instance
21
- # @return [Polar]
22
- attr_reader :polar
24
+ attr_reader :ffi_query
25
+ # @return [Host]
26
+ attr_reader :host
27
+
28
+ # Send result of predicate check across FFI boundary.
29
+ #
30
+ # @param result [Boolean]
31
+ # @param call_id [Integer]
32
+ # @raise [Error] if the FFI call raises one.
33
+ def question_result(result, call_id:)
34
+ ffi_query.question_result(result, call_id: call_id)
35
+ end
36
+
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:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
47
+ return if calls.key?(call_id)
48
+
49
+ instance = host.to_ruby(instance)
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
56
+ result = [result].to_enum unless result.is_a? Enumerator # Call must be a generator.
57
+ calls[call_id] = result.lazy
58
+ rescue ArgumentError, NoMethodError
59
+ raise InvalidCallError
60
+ end
23
61
 
24
62
  # Send next result of Ruby method call across FFI boundary.
25
63
  #
@@ -27,16 +65,24 @@ module Oso
27
65
  # @param call_id [Integer]
28
66
  # @raise [Error] if the FFI call raises one.
29
67
  def call_result(result, call_id:)
30
- ffi_instance.call_result(result, call_id: call_id)
68
+ ffi_query.call_result(result, call_id: call_id)
31
69
  end
32
70
 
33
- # Send result of predicate check across FFI boundary.
71
+ # Retrieve the next result from a registered call and pass it to {#to_polar_term}.
34
72
  #
35
- # @param result [Boolean]
36
- # @param call_id [Integer]
73
+ # @param id [Integer]
74
+ # @return [Hash]
75
+ # @raise [StopIteration] if the call has been exhausted.
76
+ def next_call_result(id)
77
+ host.to_polar_term(calls[id].next)
78
+ end
79
+
80
+ # Send application error across FFI boundary.
81
+ #
82
+ # @param message [String]
37
83
  # @raise [Error] if the FFI call raises one.
38
- def question_result(result, call_id:)
39
- ffi_instance.question_result(result, call_id: call_id)
84
+ def application_error(message)
85
+ ffi_query.application_error(message)
40
86
  end
41
87
 
42
88
  # Fetch the next result from calling a Ruby method and prepare it for
@@ -45,16 +91,17 @@ module Oso
45
91
  # @param method [#to_sym]
46
92
  # @param args [Array<Hash>]
47
93
  # @param call_id [Integer]
48
- # @param instance_id [Integer]
94
+ # @param instance [Hash<String, Object>]
49
95
  # @raise [Error] if the FFI call raises one.
50
- def handle_call(method, call_id:, instance:, args:)
51
- polar.register_call(method, call_id: call_id, instance: instance, args: args)
52
- result = JSON.dump(polar.next_call_result(call_id))
96
+ def handle_call(attribute, call_id:, instance:, args:)
97
+ register_call(attribute, call_id: call_id, instance: instance, args: args)
98
+ result = JSON.dump(next_call_result(call_id))
53
99
  call_result(result, call_id: call_id)
54
- rescue InvalidCallError, StopIteration
100
+ rescue InvalidCallError => e
101
+ application_error(e.message)
102
+ call_result(nil, call_id: call_id)
103
+ rescue StopIteration
55
104
  call_result(nil, call_id: call_id)
56
- # @TODO: polar line numbers in errors once polar errors are better.
57
- # raise PolarRuntimeError(f"Error calling {attribute}")
58
105
  end
59
106
 
60
107
  # Create a generator that can be polled to advance the query loop.
@@ -62,50 +109,63 @@ module Oso
62
109
  # @yieldparam [Hash<String, Object>]
63
110
  # @return [Enumerator]
64
111
  # @raise [Error] if any of the FFI calls raise one.
65
- def start # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
112
+ def start # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
66
113
  Enumerator.new do |yielder| # rubocop:disable Metrics/BlockLength
67
114
  loop do # rubocop:disable Metrics/BlockLength
68
- event = ffi_instance.next_event
115
+ event = ffi_query.next_event
69
116
  case event.kind
70
117
  when 'Done'
71
118
  break
72
119
  when 'Result'
73
- yielder << event.data['bindings'].transform_values { |v| polar.to_ruby(v) }
120
+ yielder << event.data['bindings'].transform_values { |v| host.to_ruby(v) }
74
121
  when 'MakeExternal'
75
122
  id = event.data['instance_id']
76
- raise DuplicateInstanceRegistrationError, id if polar.instance? id
123
+ raise DuplicateInstanceRegistrationError, id if host.instance? id
77
124
 
78
- cls_name = event.data['instance']['tag']
79
- fields = event.data['instance']['fields']['fields']
80
- polar.make_instance(cls_name, fields: fields, id: id)
125
+ constructor = event.data['constructor']['value']
126
+ if constructor.key? 'InstanceLiteral'
127
+ cls_name = constructor['InstanceLiteral']['tag']
128
+ fields = constructor['InstanceLiteral']['fields']['fields']
129
+ initargs = Hash[fields.map { |k, v| [k.to_sym, host.to_ruby(v)] }]
130
+ elsif constructor.key? 'Call'
131
+ cls_name = constructor['Call']['name']
132
+ initargs = constructor['Call']['args'].map { |arg| host.to_ruby(arg) }
133
+ else
134
+ raise InvalidConstructorError
135
+ end
136
+ host.make_instance(cls_name, initargs: initargs, id: id)
81
137
  when 'ExternalCall'
82
138
  call_id = event.data['call_id']
83
139
  instance = event.data['instance']
84
- method = event.data['attribute']
140
+ attribute = event.data['attribute']
85
141
  args = event.data['args']
86
- handle_call(method, call_id: call_id, instance: instance, args: args)
142
+ handle_call(attribute, call_id: call_id, instance: instance, args: args)
87
143
  when 'ExternalIsSubSpecializer'
88
144
  instance_id = event.data['instance_id']
89
145
  left_tag = event.data['left_class_tag']
90
146
  right_tag = event.data['right_class_tag']
91
- answer = polar.subspecializer?(instance_id, left_tag: left_tag, right_tag: right_tag)
147
+ answer = host.subspecializer?(instance_id, left_tag: left_tag, right_tag: right_tag)
92
148
  question_result(answer, call_id: event.data['call_id'])
93
149
  when 'ExternalIsa'
94
- instance_id = event.data['instance_id']
150
+ instance = event.data['instance']
95
151
  class_tag = event.data['class_tag']
96
- answer = polar.isa?(instance_id, class_tag: class_tag)
152
+ answer = host.isa?(instance, class_tag: class_tag)
97
153
  question_result(answer, call_id: event.data['call_id'])
98
154
  when 'ExternalUnify'
99
155
  left_instance_id = event.data['left_instance_id']
100
156
  right_instance_id = event.data['right_instance_id']
101
- answer = polar.unify?(left_instance_id, right_instance_id)
157
+ answer = host.unify?(left_instance_id, right_instance_id)
102
158
  question_result(answer, call_id: event.data['call_id'])
103
159
  when 'Debug'
104
160
  puts event.data['message'] if event.data['message']
105
- print '> '
106
- input = $stdin.gets.chomp!
107
- command = JSON.dump(polar.to_polar_term(input))
108
- ffi_instance.debug_command(command)
161
+ print 'debug> '
162
+ begin
163
+ input = $stdin.readline.chomp.chomp(';')
164
+ rescue EOFError
165
+ next
166
+ end
167
+ command = JSON.dump(host.to_polar_term(input))
168
+ ffi_query.debug_command(command)
109
169
  else
110
170
  raise "Unhandled event: #{JSON.dump(event.inspect)}"
111
171
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Oso
4
- VERSION = '0.2.5'
4
+ VERSION = '0.5.0'
5
5
  end
@@ -5,24 +5,26 @@ require_relative 'lib/oso/version'
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'oso-oso'
7
7
  spec.version = Oso::VERSION
8
- spec.authors = ['Oso Security']
8
+ spec.authors = ['Oso Security, Inc.']
9
9
  spec.email = ['support@osohq.com']
10
-
11
- spec.summary = 'Oso authorization API.'
10
+ spec.licenses = ['Apache-2.0']
11
+ spec.summary = 'oso authorization library.'
12
12
  spec.homepage = 'https://www.osohq.com/'
13
+
13
14
  spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
14
15
 
15
16
  spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/osohq/oso'
16
18
 
17
19
  # Specify which files should be added to the gem when it is released.
18
20
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
21
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
20
22
  files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
- files += Dir['ext/oso-oso/lib/*']
23
+ files + Dir['ext/oso-oso/lib/*']
22
24
  end
23
25
 
24
- spec.bindir = 'exe'
25
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.bindir = 'bin'
27
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
26
28
  spec.require_paths = ['lib']
27
29
 
28
30
  # Runtime dependencies
@@ -32,6 +34,7 @@ Gem::Specification.new do |spec|
32
34
  spec.add_development_dependency 'pry-byebug', '~> 3.9.0'
33
35
  spec.add_development_dependency 'rake', '~> 12.0'
34
36
  spec.add_development_dependency 'rspec', '~> 3.0'
35
- 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'
36
39
  spec.add_development_dependency 'yard', '~> 0.9.25'
37
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.2.5
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
- - Oso Security
7
+ - Oso Security, Inc.
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-21 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
@@ -66,20 +66,34 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.89.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.89.1
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: solargraph
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: 0.39.8
89
+ version: 0.39.14
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: 0.39.8
96
+ version: 0.39.14
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: yard
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -97,20 +111,21 @@ dependencies:
97
111
  description:
98
112
  email:
99
113
  - support@osohq.com
100
- executables: []
114
+ executables:
115
+ - oso
101
116
  extensions: []
102
117
  extra_rdoc_files: []
103
118
  files:
104
119
  - ".gitignore"
105
120
  - ".rspec"
121
+ - ".rubocop.yml"
106
122
  - ".solargraph.yml"
107
123
  - Gemfile
108
124
  - Gemfile.lock
109
125
  - Makefile
110
126
  - README.md
111
127
  - Rakefile
112
- - bin/console
113
- - bin/setup
128
+ - bin/oso
114
129
  - ext/oso-oso/lib/libpolar.dylib
115
130
  - ext/oso-oso/lib/libpolar.so
116
131
  - ext/oso-oso/lib/polar.dll
@@ -122,9 +137,11 @@ files:
122
137
  - lib/oso/polar/errors.rb
123
138
  - lib/oso/polar/ffi.rb
124
139
  - lib/oso/polar/ffi/error.rb
140
+ - lib/oso/polar/ffi/message.rb
125
141
  - lib/oso/polar/ffi/polar.rb
126
142
  - lib/oso/polar/ffi/query.rb
127
143
  - lib/oso/polar/ffi/query_event.rb
144
+ - lib/oso/polar/host.rb
128
145
  - lib/oso/polar/polar.rb
129
146
  - lib/oso/polar/predicate.rb
130
147
  - lib/oso/polar/query.rb
@@ -133,9 +150,11 @@ files:
133
150
  - lib/oso/version.rb
134
151
  - oso-oso.gemspec
135
152
  homepage: https://www.osohq.com/
136
- licenses: []
153
+ licenses:
154
+ - Apache-2.0
137
155
  metadata:
138
156
  homepage_uri: https://www.osohq.com/
157
+ source_code_uri: https://github.com/osohq/oso
139
158
  post_install_message:
140
159
  rdoc_options: []
141
160
  require_paths:
@@ -155,5 +174,5 @@ rubyforge_project:
155
174
  rubygems_version: 2.6.14.4
156
175
  signing_key:
157
176
  specification_version: 4
158
- summary: Oso authorization API.
177
+ summary: oso authorization library.
159
178
  test_files: []
@@ -1,33 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'bundler/setup'
5
- require 'oso'
6
-
7
- polar = Oso::Polar.new.tap do |p| # rubocop:disable Lint/UselessAssignment
8
- p.load_str('f(1); f(2); g(1); g(2); h(2); k(x) := f(x), h(x), g(x);')
9
- puts 'f(x)', p.send(:query_str, 'f(x)').to_a
10
- puts 'k(x)', p.send(:query_str, 'k(x)').to_a
11
-
12
- p.load_str('foo(1, 2); foo(3, 4); foo(5, 6);')
13
- expected = [{ 'x' => 1, 'y' => 2 }, { 'x' => 3, 'y' => 4 }, { 'x' => 5, 'y' => 6 }]
14
- raise 'AssertionError' if p.send(:query_str, 'foo(x, y)').to_a != expected
15
-
16
- class TestClass # rubocop:disable Style/Documentation
17
- def my_method
18
- 1
19
- end
20
- end
21
-
22
- p.register_class(TestClass)
23
-
24
- p.load_str('external(x, 3) := x = new TestClass{}.my_method;')
25
- results = p.send(:query_str, 'external(1, x)')
26
- p results.next
27
-
28
- # p.load_str('testDebug() := debug(), foo(x, y), k(y);')
29
- # p.send(:query_str, 'testDebug()').next
30
- end
31
-
32
- require 'pry'
33
- Pry.start
data/bin/setup DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install