oso-oso 0.2.5 → 0.5.0

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