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.
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