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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9e511f86c5a330c1bb071824398085362def8ba7
4
- data.tar.gz: da3baaad23fb6e28c8ef0558bbf1deb659c03700
3
+ metadata.gz: '09ee09c3366406a34633c870ddfe4ff271971529'
4
+ data.tar.gz: 764b0f3b440d567e19e45b71eff823b9000f35ef
5
5
  SHA512:
6
- metadata.gz: 9d55c689e8ffc0e3f7b533f54d3e622f77bd7f563182fc7b9dfc047a7a9b1554ed02711fbb8326f55fed76529dde2040d6f829bd4439a63773c9bb58ffb8c8a3
7
- data.tar.gz: 60b1c8f83803656373a108a64159636292bc91c1840292e7c01ea0c74961533d076ceaa6576010ecdf9dc062bc60c6b112e9900b726db227a322aa52c41f40e0
6
+ metadata.gz: fdae19c2528c46a85004241f0a8e59639123e416724239670cb9ff2ffc1172a87bb116b481d575e466c74f634213abdced263211f991721f1ff990e1e085d884
7
+ data.tar.gz: 309a196e2a0a3725e3bc1f45be8382ed85026dc7fc271662efaef8e207b0df2891f26da48ece702f1d9ce3e3731ea52c0a6cea30bf50af6acde54f08b44d79ec
@@ -0,0 +1,7 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.4
3
+ Exclude:
4
+ - '**/*~'
5
+ - 'bin/oso'
6
+ - 'vendor/**/*'
7
+ NewCops: enable
data/Gemfile CHANGED
@@ -3,4 +3,3 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
-
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- oso-oso (0.2.5)
4
+ oso-oso (0.5.0)
5
5
  ffi (~> 1.0)
6
6
 
7
7
  GEM
@@ -12,18 +12,18 @@ GEM
12
12
  benchmark (0.1.0)
13
13
  byebug (11.1.3)
14
14
  coderay (1.1.3)
15
- diff-lcs (1.3)
15
+ diff-lcs (1.4.4)
16
16
  e2mmap (0.1.0)
17
17
  ffi (1.13.1)
18
18
  jaro_winkler (1.5.4)
19
19
  maruku (0.7.3)
20
20
  method_source (1.0.0)
21
21
  mini_portile2 (2.4.0)
22
- nokogiri (1.10.9)
22
+ nokogiri (1.10.10)
23
23
  mini_portile2 (~> 2.4.0)
24
- parallel (1.19.1)
25
- parser (2.7.1.3)
26
- ast (~> 2.4.0)
24
+ parallel (1.19.2)
25
+ parser (2.7.1.4)
26
+ ast (~> 2.4.1)
27
27
  pry (0.13.1)
28
28
  coderay (~> 1.1)
29
29
  method_source (~> 1.0)
@@ -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.85.1)
52
+ rubocop (0.89.1)
53
53
  parallel (~> 1.10)
54
- parser (>= 2.7.0.1)
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.0.3)
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
- rubocop-ast (0.0.3)
62
- parser (>= 2.7.0.1)
61
+ rubocop-ast (0.3.0)
62
+ parser (>= 2.7.1.4)
63
63
  ruby-progressbar (1.10.1)
64
- solargraph (0.39.8)
64
+ solargraph (0.39.14)
65
65
  backport (~> 1.1)
66
66
  benchmark
67
67
  bundler (>= 1.17.2)
@@ -88,7 +88,8 @@ DEPENDENCIES
88
88
  pry-byebug (~> 3.9.0)
89
89
  rake (~> 12.0)
90
90
  rspec (~> 3.0)
91
- solargraph (~> 0.39.8)
91
+ rubocop (~> 0.89.1)
92
+ solargraph (~> 0.39.14)
92
93
  yard (~> 0.9.25)
93
94
 
94
95
  BUNDLED WITH
data/Makefile CHANGED
@@ -1,4 +1,4 @@
1
- .PHONY: install rust test
1
+ .PHONY: rust install test lint typecheck repl
2
2
 
3
3
  rust:
4
4
  $(MAKE) -C ../.. rust-build
@@ -8,3 +8,12 @@ install: rust
8
8
 
9
9
  test: install
10
10
  bundle exec rake spec
11
+
12
+ lint:
13
+ bundle exec rubocop
14
+
15
+ typecheck:
16
+ bundle exec solargraph typecheck
17
+
18
+ repl: install
19
+ bundle exec oso
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Oso::Oso
1
+ # oso-oso
2
2
 
3
3
  ## Installation
4
4
 
@@ -18,13 +18,10 @@ Or install it yourself as:
18
18
 
19
19
  ## Development
20
20
 
21
- After checking out the repo, run `bin/setup` to install dependencies. Then, run
22
- `rake spec` to run the tests. You can also run `bin/console` for an interactive
23
- prompt that will allow you to experiment.
21
+ After checking out the repo, run `bundle install` to install dependencies.
22
+ Then, run `bundle exec rake spec` to run the tests. You can also run `bundle
23
+ exec oso` for an interactive REPL that will allow you to experiment.
24
24
 
25
- To install this gem onto your local machine, run `bundle exec rake install`. To
26
- release a new version, update the version number in `version.rb`, and then run
27
- `bundle exec rake release`, which will create a git tag for the version, push
28
- git commits and tags, and push the `.gem` file to
29
- [rubygems.org](https://rubygems.org).
25
+ To install this gem onto your local machine, run `bundle exec rake install`.
30
26
 
27
+ New releases are minted and pushed to RubyGems via GitHub Actions workflows.
data/Rakefile CHANGED
@@ -6,4 +6,3 @@ require 'rspec/core/rake_task'
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  task default: :spec
9
-
data/bin/oso ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env -S bundle exec ruby
2
+ #-*-ruby-*-
3
+
4
+ require 'bundler/setup'
5
+ require 'oso'
6
+
7
+ Oso.new.repl(ARGV)
Binary file
Binary file
@@ -3,7 +3,7 @@
3
3
  module Oso
4
4
  # An HTTP resource.
5
5
  class Http
6
- def initialize(hostname: nil, path: nil, query: nil)
6
+ def initialize(hostname, path, query)
7
7
  @hostname = hostname
8
8
  @path = path
9
9
  @query = query
@@ -1,47 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'polar/polar'
4
+
3
5
  module Oso
4
- # Oso authorization API.
5
- class Oso
6
+ # oso authorization API.
7
+ class Oso < Polar::Polar
6
8
  def initialize
7
- @polar = ::Oso::Polar.new
9
+ super
8
10
  register_class(Http, name: 'Http')
9
11
  register_class(PathMapper, name: 'PathMapper')
10
12
  end
11
13
 
12
- def load_file(file)
13
- polar.load_file(file)
14
- end
15
-
16
- def load_str(str)
17
- polar.load_str(str)
18
- end
19
-
20
- def register_class(cls, name: nil) # rubocop:disable Naming/MethodParameterName
21
- if block_given?
22
- polar.register_class(cls, name: name, from_polar: Proc.new)
23
- else
24
- polar.register_class(cls, name: name)
25
- end
26
- end
27
-
28
- def allow(actor:, action:, resource:)
29
- polar.query_pred('allow', args: [actor, action, resource]).next
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.
21
+ def allowed?(actor:, action:, resource:)
22
+ query_rule('allow', actor, action, resource).next
30
23
  true
31
24
  rescue StopIteration
32
25
  false
33
26
  end
34
-
35
- def query_predicate(name, *args)
36
- polar.query_pred(name, args: args)
37
- end
38
-
39
- def load_queued_files
40
- polar.load_queued_files
41
- end
42
-
43
- private
44
-
45
- attr_reader :polar
46
27
  end
47
28
  end
@@ -4,7 +4,7 @@ module Oso
4
4
  # Map from a template string with capture groups of the form
5
5
  # `{name}` to a dictionary of the form `{name: captured_value}`
6
6
  class PathMapper
7
- def initialize(template:)
7
+ def initialize(template)
8
8
  capture_group = /({([^}]+)})/
9
9
 
10
10
  template = template.dup
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'oso/polar/errors'
4
4
  require 'oso/polar/ffi'
5
+ require 'oso/polar/host'
5
6
  require 'oso/polar/polar'
6
7
  require 'oso/polar/predicate'
7
8
  require 'oso/polar/query'
@@ -5,16 +5,12 @@ module Oso
5
5
  # Base error type for Oso::Polar.
6
6
  class Error < ::RuntimeError
7
7
  attr_reader :stack_trace
8
-
8
+
9
9
  # @param message [String]
10
10
  # @param details [Hash]
11
11
  def initialize(message = nil, details: nil)
12
12
  @details = details
13
- if details and details.key?("stack_trace")
14
- @stack_trace = details['stack_trace']
15
- else
16
- @stack_trace = nil
17
- end
13
+ @stack_trace = details&.fetch('stack_trace', nil)
18
14
  super(message)
19
15
  end
20
16
  end
@@ -40,12 +36,32 @@ module Oso
40
36
  class DuplicateInstanceRegistrationError < PolarRuntimeError; end
41
37
  class InvalidCallError < PolarRuntimeError; end
42
38
  class InvalidConstructorError < PolarRuntimeError; end
39
+ class InvalidQueryTypeError < PolarRuntimeError; end
43
40
  class InlineQueryFailedError < PolarRuntimeError; end
44
41
  class NullByteInPolarFileError < PolarRuntimeError; end
45
42
  class UnexpectedPolarTypeError < PolarRuntimeError; end
43
+ class PolarFileAlreadyLoadedError < PolarRuntimeError # rubocop:disable Style/Documentation
44
+ # @param file [String]
45
+ def initialize(file)
46
+ super("File #{file} has already been loaded.")
47
+ end
48
+ end
49
+ class PolarFileContentsChangedError < PolarRuntimeError # rubocop:disable Style/Documentation
50
+ # @param file [String]
51
+ def initialize(file)
52
+ super("A file with the name #{file}, but different contents, has already been loaded.")
53
+ end
54
+ end
55
+ class PolarFileNameChangedError < PolarRuntimeError # rubocop:disable Style/Documentation
56
+ # @param file [String]
57
+ # @param existing [String]
58
+ def initialize(file, existing)
59
+ super("A file with the same contents as #{file} named #{existing} has already been loaded.")
60
+ end
61
+ end
46
62
  class PolarFileExtensionError < PolarRuntimeError # rubocop:disable Style/Documentation
47
- def initialize
48
- super('Polar files must have .pol or .polar extension.')
63
+ def initialize(file)
64
+ super("Polar files must have .polar extension. Offending file: #{file}")
49
65
  end
50
66
  end
51
67
  class PolarFileNotFoundError < PolarRuntimeError # rubocop:disable Style/Documentation
@@ -58,7 +74,7 @@ module Oso
58
74
  # @param as [String]
59
75
  # @param old [Class]
60
76
  # @param new [Class]
61
- def initialize(name:, old:, new:) # rubocop:disable Naming/MethodParameterName
77
+ def initialize(name:, old:, new:)
62
78
  super("Attempted to alias #{new} as '#{name}', but #{old} already has that alias.")
63
79
  end
64
80
  end
@@ -5,10 +5,12 @@ require 'ffi'
5
5
  module Oso
6
6
  module Polar
7
7
  module FFI
8
- LIB = ::FFI::Platform::LIBPREFIX + 'polar.' + ::FFI::Platform::LIBSUFFIX
9
- LIB_PATH = File.expand_path(File.join(__dir__, "../../../ext/oso-oso/lib/#{LIB}"))
10
- # @TODO: Fall back to this if there's no release build libs. Easier for dev.
11
- # LIB_PATH = File.expand_path(File.join(__dir__, "../../../../../target/debug/#{LIB}"))
8
+ LIB = "#{::FFI::Platform::LIBPREFIX}polar.#{::FFI::Platform::LIBSUFFIX}"
9
+ RELEASE_PATH = File.expand_path(File.join(__dir__, "../../../ext/oso-oso/lib/#{LIB}"))
10
+ DEV_PATH = File.expand_path(File.join(__dir__, "../../../../../target/debug/#{LIB}"))
11
+ # If the lib exists in the ext/ dir, use it. Otherwise, fall back to
12
+ # checking the local Rust target dir.
13
+ LIB_PATH = File.file?(RELEASE_PATH) ? RELEASE_PATH : DEV_PATH
12
14
 
13
15
  # Wrapper classes defined upfront to fix Ruby loading issues. Actual
14
16
  # implementations live in the sibling `ffi/` directory and are `require`d
@@ -35,7 +37,13 @@ module Oso
35
37
  # Wrapper class for Error FFI pointer + operations.
36
38
  class Error < ::FFI::AutoPointer
37
39
  def self.release(ptr)
38
- 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?
39
47
  end
40
48
  end
41
49
  end
@@ -47,3 +55,4 @@ require 'oso/polar/ffi/polar'
47
55
  require 'oso/polar/ffi/query'
48
56
  require 'oso/polar/ffi/query_event'
49
57
  require 'oso/polar/ffi/error'
58
+ require 'oso/polar/ffi/message'
@@ -22,7 +22,7 @@ module Oso
22
22
  #
23
23
  # @return [::Oso::Polar::Error] if there's an FFI error.
24
24
  # @return [::Oso::Polar::FFIErrorNotFound] if there isn't one.
25
- def self.get # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
25
+ def self.get # rubocop:disable Metrics/MethodLength
26
26
  error = Rust.get
27
27
  return ::Oso::Polar::FFIErrorNotFound if error.null?
28
28
 
@@ -48,7 +48,7 @@ module Oso
48
48
  # @param msg [String]
49
49
  # @param details [Hash<String, Object>]
50
50
  # @return [::Oso::Polar::ParseError] the object converted into the expected format.
51
- private_class_method def self.parse_error(kind, msg:, details:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
51
+ private_class_method def self.parse_error(kind, msg:, details:) # rubocop:disable Metrics/MethodLength
52
52
  case kind
53
53
  when 'ExtraToken'
54
54
  ::Oso::Polar::ParseError::ExtraToken.new(msg, details: details)
@@ -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
@@ -11,12 +11,12 @@ module Oso
11
11
 
12
12
  attach_function :new, :polar_new, [], FFI::Polar
13
13
  attach_function :load_str, :polar_load, [FFI::Polar, :string, :string], :int32
14
- attach_function :next_inline_query, :polar_next_inline_query, [FFI::Polar], FFI::Query
14
+ attach_function :next_inline_query, :polar_next_inline_query, [FFI::Polar, :uint32], FFI::Query
15
15
  attach_function :new_id, :polar_get_external_id, [FFI::Polar], :uint64
16
- attach_function :new_query_from_str, :polar_new_query, [FFI::Polar, :string], FFI::Query
17
- attach_function :new_query_from_term, :polar_new_query_from_term, [FFI::Polar, :string], FFI::Query
18
- attach_function :new_query_from_repl, :polar_query_from_repl, [FFI::Polar], FFI::Query
16
+ attach_function :new_query_from_str, :polar_new_query, [FFI::Polar, :string, :uint32], FFI::Query
17
+ attach_function :new_query_from_term, :polar_new_query_from_term, [FFI::Polar, :string, :uint32], FFI::Query
19
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
20
20
  attach_function :free, :polar_free, [FFI::Polar], :int32
21
21
  end
22
22
  private_constant :Rust
@@ -34,14 +34,17 @@ module Oso
34
34
  # @param filename [String]
35
35
  # @raise [FFI::Error] if the FFI call returns an error.
36
36
  def load_str(src, filename: nil)
37
- 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?
38
40
  end
39
41
 
40
42
  # @return [FFI::Query] if there are remaining inline queries.
41
43
  # @return [nil] if there are no remaining inline queries.
42
44
  # @raise [FFI::Error] if the FFI call returns an error.
43
45
  def next_inline_query
44
- query = Rust.next_inline_query(self)
46
+ query = Rust.next_inline_query(self, 0)
47
+ process_messages
45
48
  query.null? ? nil : query
46
49
  end
47
50
 
@@ -60,7 +63,8 @@ module Oso
60
63
  # @return [FFI::Query]
61
64
  # @raise [FFI::Error] if the FFI call returns an error.
62
65
  def new_query_from_str(str)
63
- query = Rust.new_query_from_str(self, str)
66
+ query = Rust.new_query_from_str(self, str, 0)
67
+ process_messages
64
68
  raise FFI::Error.get if query.null?
65
69
 
66
70
  query
@@ -70,16 +74,8 @@ module Oso
70
74
  # @return [FFI::Query]
71
75
  # @raise [FFI::Error] if the FFI call returns an error.
72
76
  def new_query_from_term(term)
73
- query = Rust.new_query_from_term(self, JSON.dump(term))
74
- raise FFI::Error.get if query.null?
75
-
76
- query
77
- end
78
-
79
- # @return [FFI::Query]
80
- # @raise [FFI::Error] if the FFI call returns an error.
81
- def new_query_from_repl
82
- query = Rust.new_query_from_repl(self)
77
+ query = Rust.new_query_from_term(self, JSON.dump(term), 0)
78
+ process_messages
83
79
  raise FFI::Error.get if query.null?
84
80
 
85
81
  query
@@ -89,7 +85,21 @@ module Oso
89
85
  # @param value [Hash<String, Object>]
90
86
  # @raise [FFI::Error] if the FFI call returns an error.
91
87
  def register_constant(name, value:)
92
- 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
93
103
  end
94
104
  end
95
105
  end