oso-oso 0.2.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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