oso-oso 0.2.2 → 0.4.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,57 @@
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(method, call_id:, instance:, args:)
47
+ return if calls.key?(call_id)
48
+
49
+ args = args.map { |a| host.to_ruby(a) }
50
+ instance = host.to_ruby(instance)
51
+ result = instance.__send__(method, *args)
52
+ result = [result].to_enum unless result.is_a? Enumerator # Call must be a generator.
53
+ calls[call_id] = result.lazy
54
+ rescue ArgumentError, NoMethodError
55
+ raise InvalidCallError
56
+ end
23
57
 
24
58
  # Send next result of Ruby method call across FFI boundary.
25
59
  #
@@ -27,16 +61,24 @@ module Oso
27
61
  # @param call_id [Integer]
28
62
  # @raise [Error] if the FFI call raises one.
29
63
  def call_result(result, call_id:)
30
- ffi_instance.call_result(result, call_id: call_id)
64
+ ffi_query.call_result(result, call_id: call_id)
31
65
  end
32
66
 
33
- # Send result of predicate check across FFI boundary.
67
+ # Retrieve the next result from a registered call and pass it to {#to_polar_term}.
34
68
  #
35
- # @param result [Boolean]
36
- # @param call_id [Integer]
69
+ # @param id [Integer]
70
+ # @return [Hash]
71
+ # @raise [StopIteration] if the call has been exhausted.
72
+ def next_call_result(id)
73
+ host.to_polar_term(calls[id].next)
74
+ end
75
+
76
+ # Send application error across FFI boundary.
77
+ #
78
+ # @param message [String]
37
79
  # @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)
80
+ def application_error(message)
81
+ ffi_query.application_error(message)
40
82
  end
41
83
 
42
84
  # Fetch the next result from calling a Ruby method and prepare it for
@@ -45,16 +87,17 @@ module Oso
45
87
  # @param method [#to_sym]
46
88
  # @param args [Array<Hash>]
47
89
  # @param call_id [Integer]
48
- # @param instance_id [Integer]
90
+ # @param instance [Hash<String, Object>]
49
91
  # @raise [Error] if the FFI call raises one.
50
92
  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))
93
+ register_call(method, call_id: call_id, instance: instance, args: args)
94
+ result = JSON.dump(next_call_result(call_id))
53
95
  call_result(result, call_id: call_id)
54
- rescue InvalidCallError, StopIteration
96
+ rescue InvalidCallError => e
97
+ application_error(e.message)
98
+ call_result(nil, call_id: call_id)
99
+ rescue StopIteration
55
100
  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
101
  end
59
102
 
60
103
  # Create a generator that can be polled to advance the query loop.
@@ -62,22 +105,31 @@ module Oso
62
105
  # @yieldparam [Hash<String, Object>]
63
106
  # @return [Enumerator]
64
107
  # @raise [Error] if any of the FFI calls raise one.
65
- def start # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
108
+ def start # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
66
109
  Enumerator.new do |yielder| # rubocop:disable Metrics/BlockLength
67
110
  loop do # rubocop:disable Metrics/BlockLength
68
- event = ffi_instance.next_event
111
+ event = ffi_query.next_event
69
112
  case event.kind
70
113
  when 'Done'
71
114
  break
72
115
  when 'Result'
73
- yielder << event.data['bindings'].transform_values { |v| polar.to_ruby(v) }
116
+ yielder << event.data['bindings'].transform_values { |v| host.to_ruby(v) }
74
117
  when 'MakeExternal'
75
118
  id = event.data['instance_id']
76
- raise DuplicateInstanceRegistrationError, id if polar.instance? id
119
+ raise DuplicateInstanceRegistrationError, id if host.instance? id
77
120
 
78
- cls_name = event.data['instance']['tag']
79
- fields = event.data['instance']['fields']['fields']
80
- polar.make_instance(cls_name, fields: fields, id: id)
121
+ constructor = event.data['constructor']['value']
122
+ if constructor.key? 'InstanceLiteral'
123
+ cls_name = constructor['InstanceLiteral']['tag']
124
+ fields = constructor['InstanceLiteral']['fields']['fields']
125
+ initargs = Hash[fields.map { |k, v| [k.to_sym, host.to_ruby(v)] }]
126
+ elsif constructor.key? 'Call'
127
+ cls_name = constructor['Call']['name']
128
+ initargs = constructor['Call']['args'].map { |arg| host.to_ruby(arg) }
129
+ else
130
+ raise InvalidConstructorError
131
+ end
132
+ host.make_instance(cls_name, initargs: initargs, id: id)
81
133
  when 'ExternalCall'
82
134
  call_id = event.data['call_id']
83
135
  instance = event.data['instance']
@@ -88,24 +140,28 @@ module Oso
88
140
  instance_id = event.data['instance_id']
89
141
  left_tag = event.data['left_class_tag']
90
142
  right_tag = event.data['right_class_tag']
91
- answer = polar.subspecializer?(instance_id, left_tag: left_tag, right_tag: right_tag)
143
+ answer = host.subspecializer?(instance_id, left_tag: left_tag, right_tag: right_tag)
92
144
  question_result(answer, call_id: event.data['call_id'])
93
145
  when 'ExternalIsa'
94
- instance_id = event.data['instance_id']
146
+ instance = event.data['instance']
95
147
  class_tag = event.data['class_tag']
96
- answer = polar.isa?(instance_id, class_tag: class_tag)
148
+ answer = host.isa?(instance, class_tag: class_tag)
97
149
  question_result(answer, call_id: event.data['call_id'])
98
150
  when 'ExternalUnify'
99
151
  left_instance_id = event.data['left_instance_id']
100
152
  right_instance_id = event.data['right_instance_id']
101
- answer = polar.unify?(left_instance_id, right_instance_id)
153
+ answer = host.unify?(left_instance_id, right_instance_id)
102
154
  question_result(answer, call_id: event.data['call_id'])
103
155
  when 'Debug'
104
156
  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)
157
+ print 'debug> '
158
+ begin
159
+ input = $stdin.readline.chomp.chomp(';')
160
+ rescue EOFError
161
+ next
162
+ end
163
+ command = JSON.dump(host.to_polar_term(input))
164
+ ffi_query.debug_command(command)
109
165
  else
110
166
  raise "Unhandled event: #{JSON.dump(event.inspect)}"
111
167
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Oso
4
- VERSION = '0.2.2'
4
+ VERSION = '0.4.0'
5
5
  end
@@ -5,22 +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
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ files + Dir['ext/oso-oso/lib/*']
21
24
  end
22
- spec.bindir = 'exe'
23
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+
26
+ spec.bindir = 'bin'
27
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
24
28
  spec.require_paths = ['lib']
25
29
 
26
30
  # Runtime dependencies
@@ -30,6 +34,7 @@ Gem::Specification.new do |spec|
30
34
  spec.add_development_dependency 'pry-byebug', '~> 3.9.0'
31
35
  spec.add_development_dependency 'rake', '~> 12.0'
32
36
  spec.add_development_dependency 'rspec', '~> 3.0'
37
+ spec.add_development_dependency 'rubocop', '~> 0.89.0'
33
38
  spec.add_development_dependency 'solargraph', '~> 0.39.8'
34
39
  spec.add_development_dependency 'yard', '~> 0.9.25'
35
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.2
4
+ version: 0.4.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-17 00:00:00.000000000 Z
11
+ date: 2020-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -66,6 +66,20 @@ 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.0
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.0
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: solargraph
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -97,22 +111,24 @@ 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
131
+ - ext/oso-oso/lib/polar.dll
116
132
  - lib/oso.rb
117
133
  - lib/oso/http.rb
118
134
  - lib/oso/oso.rb
@@ -124,6 +140,7 @@ files:
124
140
  - lib/oso/polar/ffi/polar.rb
125
141
  - lib/oso/polar/ffi/query.rb
126
142
  - lib/oso/polar/ffi/query_event.rb
143
+ - lib/oso/polar/host.rb
127
144
  - lib/oso/polar/polar.rb
128
145
  - lib/oso/polar/predicate.rb
129
146
  - lib/oso/polar/query.rb
@@ -132,9 +149,11 @@ files:
132
149
  - lib/oso/version.rb
133
150
  - oso-oso.gemspec
134
151
  homepage: https://www.osohq.com/
135
- licenses: []
152
+ licenses:
153
+ - Apache-2.0
136
154
  metadata:
137
155
  homepage_uri: https://www.osohq.com/
156
+ source_code_uri: https://github.com/osohq/oso
138
157
  post_install_message:
139
158
  rdoc_options: []
140
159
  require_paths:
@@ -150,8 +169,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
150
169
  - !ruby/object:Gem::Version
151
170
  version: '0'
152
171
  requirements: []
153
- rubygems_version: 3.1.4
172
+ rubyforge_project:
173
+ rubygems_version: 2.6.14.4
154
174
  signing_key:
155
175
  specification_version: 4
156
- summary: Oso authorization API.
176
+ summary: oso authorization library.
157
177
  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