oso-oso 0.20.0.pre.beta → 0.20.1.pre.beta

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: aa0564b86f2ceb4b54a62cdbae1cf2a3cc14ee91
4
- data.tar.gz: d0cbc579ac5bd7284b559d3ca3d31af9f6837e96
3
+ metadata.gz: 7a6c6bd2d2fb38245a5103c0153f943f51060c90
4
+ data.tar.gz: 33dcb41c1c6bd0aeeec56d88391e1e80a66b6061
5
5
  SHA512:
6
- metadata.gz: d2a7f469cff84febf8dd66f737384523fd576d42b1d44e41247bb738a7b26ca230d9e91066be2acf2a6ade07be780aeec5fa529b21c88c7d0bb626ca66ab821a
7
- data.tar.gz: 742452956eeb29797b173888a268962bbc6e92e88b7148e7ca3952bb9d9c7687abd55d74c657d170056a61e911ed0183d29fa3ef471aa5c8cd733f47d72d188b
6
+ metadata.gz: 5c025329ad3a4eff57b2cf5e7d5ca8430ef825ee287111690f06439a7c6f623068048d27ea70358af8ffb8f9d935b926cdecf54baa848f95da9ebaa23b0c0e39
7
+ data.tar.gz: 2c4a4114eafb5887805a7b92015f466296ae9eaaa4ffe19f1cc60d4f2ec59b6b580ff80ebe60143e639d7f606d79f343c89e3fdd7e7a52789939af9716b9b2c3
data/.rubocop.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 2.4
3
3
  Exclude:
4
- - '**/*~'
5
- - 'bin/oso'
6
- - 'vendor/**/*'
4
+ - "**/*~"
5
+ - "bin/oso"
6
+ - "vendor/**/*"
7
7
  NewCops: enable
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- oso-oso (0.20.0.pre.beta)
4
+ oso-oso (0.20.1.pre.beta)
5
5
  ffi (~> 1.0)
6
6
 
7
7
  GEM
@@ -19,15 +19,15 @@ GEM
19
19
  minitest (~> 5.1)
20
20
  tzinfo (~> 1.1)
21
21
  arel (9.0.0)
22
- ast (2.4.1)
23
- backport (1.1.2)
24
- benchmark (0.1.0)
22
+ ast (2.4.2)
23
+ backport (1.2.0)
24
+ benchmark (0.1.1)
25
25
  byebug (11.1.3)
26
26
  coderay (1.1.3)
27
27
  concurrent-ruby (1.1.9)
28
28
  diff-lcs (1.4.4)
29
29
  e2mmap (0.1.0)
30
- ffi (1.15.3)
30
+ ffi (1.15.4)
31
31
  i18n (1.8.10)
32
32
  concurrent-ruby (~> 1.0)
33
33
  jaro_winkler (1.5.4)
@@ -37,8 +37,8 @@ GEM
37
37
  minitest (5.14.4)
38
38
  nokogiri (1.10.10)
39
39
  mini_portile2 (~> 2.4.0)
40
- parallel (1.19.2)
41
- parser (2.7.1.4)
40
+ parallel (1.20.1)
41
+ parser (2.7.2.0)
42
42
  ast (~> 2.4.1)
43
43
  pry (0.13.1)
44
44
  coderay (~> 1.1)
@@ -48,23 +48,23 @@ GEM
48
48
  pry (~> 0.13.0)
49
49
  rainbow (3.0.0)
50
50
  rake (12.3.3)
51
- regexp_parser (1.7.1)
51
+ regexp_parser (2.1.1)
52
52
  reverse_markdown (2.0.0)
53
53
  nokogiri
54
- rexml (3.2.4)
55
- rspec (3.9.0)
56
- rspec-core (~> 3.9.0)
57
- rspec-expectations (~> 3.9.0)
58
- rspec-mocks (~> 3.9.0)
59
- rspec-core (3.9.2)
60
- rspec-support (~> 3.9.3)
61
- rspec-expectations (3.9.2)
54
+ rexml (3.2.5)
55
+ rspec (3.10.0)
56
+ rspec-core (~> 3.10.0)
57
+ rspec-expectations (~> 3.10.0)
58
+ rspec-mocks (~> 3.10.0)
59
+ rspec-core (3.10.1)
60
+ rspec-support (~> 3.10.0)
61
+ rspec-expectations (3.10.1)
62
62
  diff-lcs (>= 1.2.0, < 2.0)
63
- rspec-support (~> 3.9.0)
64
- rspec-mocks (3.9.1)
63
+ rspec-support (~> 3.10.0)
64
+ rspec-mocks (3.10.2)
65
65
  diff-lcs (>= 1.2.0, < 2.0)
66
- rspec-support (~> 3.9.0)
67
- rspec-support (3.9.3)
66
+ rspec-support (~> 3.10.0)
67
+ rspec-support (3.10.2)
68
68
  rubocop (0.89.1)
69
69
  parallel (~> 1.10)
70
70
  parser (>= 2.7.1.1)
@@ -74,10 +74,10 @@ GEM
74
74
  rubocop-ast (>= 0.3.0, < 1.0)
75
75
  ruby-progressbar (~> 1.7)
76
76
  unicode-display_width (>= 1.4.0, < 2.0)
77
- rubocop-ast (0.3.0)
78
- parser (>= 2.7.1.4)
79
- ruby-progressbar (1.10.1)
80
- solargraph (0.39.14)
77
+ rubocop-ast (0.8.0)
78
+ parser (>= 2.7.1.5)
79
+ ruby-progressbar (1.11.0)
80
+ solargraph (0.39.17)
81
81
  backport (~> 1.1)
82
82
  benchmark
83
83
  bundler (>= 1.17.2)
@@ -92,16 +92,17 @@ GEM
92
92
  tilt (~> 2.0)
93
93
  yard (~> 0.9, >= 0.9.24)
94
94
  sqlite3 (1.4.2)
95
- thor (1.0.1)
95
+ thor (1.1.0)
96
96
  thread_safe (0.3.6)
97
97
  tilt (2.0.10)
98
98
  tzinfo (1.2.9)
99
99
  thread_safe (~> 0.1)
100
100
  unicode-display_width (1.7.0)
101
- yard (0.9.25)
101
+ yard (0.9.26)
102
102
 
103
103
  PLATFORMS
104
104
  ruby
105
+ x86_64-darwin-20
105
106
 
106
107
  DEPENDENCIES
107
108
  activerecord
@@ -115,4 +116,4 @@ DEPENDENCIES
115
116
  yard (~> 0.9.25)
116
117
 
117
118
  BUNDLED WITH
118
- 2.2.4
119
+ 2.2.15
data/README.md CHANGED
@@ -4,9 +4,7 @@
4
4
 
5
5
  Add this line to your application's Gemfile:
6
6
 
7
- ```ruby
8
- gem 'oso-oso'
9
- ```
7
+ gem 'oso-oso'
10
8
 
11
9
  And then execute:
12
10
 
Binary file
Binary file
Binary file
data/lib/oso/errors.rb ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Oso
4
+ class Error < ::RuntimeError
5
+ end
6
+
7
+ class AuthorizationError < Error
8
+ end
9
+
10
+ # Thrown by the +authorize+, +authorize_field+, and +authorize_request+
11
+ # methods when the action is not allowed.
12
+ #
13
+ # Most of the time, your app should handle this error by returning a 403 HTTP
14
+ # error to the client.
15
+ class ForbiddenError < AuthorizationError
16
+ def initialize
17
+ super(
18
+ 'Oso ForbiddenError -- The requested action was not allowed for the ' \
19
+ 'given resource. You should handle this error by returning a 403 error ' \
20
+ 'to the client.'
21
+ )
22
+ end
23
+ end
24
+
25
+ # Thrown by the +authorize+ method of an +Oso+ instance. This error indicates
26
+ # that the actor is not only not allowed to perform the given action, but also
27
+ # is not allowed to +"read"+ the given resource.
28
+ #
29
+ # Most of the time, your app should handle this error by returning a 404 HTTP
30
+ # error to the client.
31
+ #
32
+ # To control which action is used for the distinction between
33
+ # +NotFoundError+ and +ForbiddenError+, you can customize the
34
+ # +read_action+ on your +Oso+ instance.
35
+ class NotFoundError < AuthorizationError
36
+ def initialize
37
+ super(
38
+ 'Oso NotFoundError -- The current user does not have permission to read ' \
39
+ 'the given resource. You should handle this error by returning a 404 ' \
40
+ 'error to the client.'
41
+ )
42
+ end
43
+ end
44
+ end
data/lib/oso/oso.rb CHANGED
@@ -1,12 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
3
4
  require_relative 'polar/polar'
4
5
 
5
6
  module Oso
6
7
  # oso authorization API.
7
8
  class Oso < Polar::Polar
8
- def initialize
9
- super
9
+ # Create an Oso instance, which is used to configure and enforce an Oso
10
+ # policy in an app.
11
+ #
12
+ # @param forbidden_error [Class] Optionally override the "forbidden" error
13
+ # class thrown by the `authorize*` methods. Defaults to
14
+ # {Oso::ForbiddenError}.
15
+ # @param not_found_error [Class] Optionally override the "not found" error
16
+ # class thrown by {#authorize}. Defaults to {Oso::NotFoundError}.
17
+ # @param read_action The action used by the {#authorize} method to
18
+ # determine whether an authorization failure should
19
+ # raise a {Oso::NotFoundError} or a {Oso::ForbiddenError}
20
+ def initialize(not_found_error: NotFoundError, forbidden_error: ForbiddenError, read_action: 'read')
21
+ super()
22
+ @not_found_error = not_found_error
23
+ @forbidden_error = forbidden_error
24
+ @read_action = read_action
10
25
  end
11
26
 
12
27
  # Query the knowledge base to determine whether an actor is allowed to
@@ -17,7 +32,191 @@ module Oso
17
32
  # @param resource [Object] Object.
18
33
  # @return [Boolean] An access control decision.
19
34
  def allowed?(actor:, action:, resource:)
20
- !query_rule('allow', actor, action, resource).first.nil?
35
+ query_rule_once('allow', actor, action, resource)
36
+ end
37
+
38
+ # Ensure that +actor+ is allowed to perform +action+ on
39
+ # +resource+.
40
+ #
41
+ # If the action is permitted with an +allow+ rule in the policy, then
42
+ # this method returns +None+. If the action is not permitted by the
43
+ # policy, this method will raise an error.
44
+ #
45
+ # The error raised by this method depends on whether the actor can perform
46
+ # the +"read"+ action on the resource. If they cannot read the resource,
47
+ # then a {Oso::NotFoundError} error is raised. Otherwise, a
48
+ # {Oso::ForbiddenError} is raised.
49
+ #
50
+ # @param actor The actor performing the request.
51
+ # @param action The action the actor is attempting to perform.
52
+ # @param resource The resource being accessed.
53
+ # @param check_read [Boolean] If set to +false+, a {Oso::ForbiddenError} is
54
+ # always thrown on authorization failures, regardless of whether the actor
55
+ # can read the resource. Default is +true+.
56
+ #
57
+ # @raise [Oso::ForbiddenError] Raised if the actor does not have permission
58
+ # to perform this action on this resource, but _does_ have +"read"+
59
+ # permission on the resource.
60
+ # @raise [Oso::NotFoundError] Raised if the actor does not have permission
61
+ # to perform this action on this resource and additionally does not have
62
+ # permission to +"read"+ the resource.
63
+ def authorize(actor, action, resource, check_read: true)
64
+ return if query_rule_once('allow', actor, action, resource)
65
+
66
+ if check_read && (action == @read_action || !query_rule_once('allow', actor, @read_action, resource))
67
+ raise @not_found_error
68
+ end
69
+
70
+ raise @forbidden_error
71
+ end
72
+
73
+ # Ensure that +actor+ is allowed to send +request+ to the server.
74
+ #
75
+ # Checks the +allow_request+ rule of a policy.
76
+ #
77
+ # If the request is permitted with an +allow_request+ rule in the
78
+ # policy, then this method returns nothing. Otherwise, this method raises
79
+ # a {Oso::ForbiddenError}.
80
+ #
81
+ # @param actor The actor performing the request.
82
+ # @param request An object representing the request that was sent by the
83
+ # actor.
84
+ #
85
+ # @raise [Oso::ForbiddenError] Raised if the actor does not have permission
86
+ # to send the request.
87
+ def authorize_request(actor, request)
88
+ raise @forbidden_error unless query_rule_once('allow_request', actor, request)
89
+ end
90
+
91
+ # Ensure that +actor+ is allowed to perform +action+ on a given
92
+ # +resource+'s +field+.
93
+ #
94
+ # If the action is permitted by an +allow_field+ rule in the policy,
95
+ # then this method returns nothing. If the action is not permitted by the
96
+ # policy, this method will raise a {Oso::ForbiddenError}.
97
+ #
98
+ # @param actor The actor performing the request.
99
+ # @param action The action the actor is attempting to perform on the
100
+ # field.
101
+ # @param resource The resource being accessed.
102
+ # @param field The name of the field being accessed.
103
+ #
104
+ # @raise [Oso::ForbiddenError] Raised if the actor does not have permission
105
+ # to access this field.
106
+ def authorize_field(actor, action, resource, field)
107
+ raise @forbidden_error unless query_rule_once('allow_field', actor, action, resource, field)
108
+ end
109
+
110
+ # Determine the actions +actor+ is allowed to take on +resource+.
111
+ #
112
+ # Collects all actions allowed by allow rules in the Polar policy for the
113
+ # given combination of actor and resource.
114
+ #
115
+ # @param actor The actor for whom to collect allowed actions
116
+ # @param resource The resource being accessed
117
+ # @param allow_wildcard Flag to determine behavior if the policy
118
+ # includes a wildcard action. E.g., a rule allowing any action:
119
+ # +allow(_actor, _action, _resource)+. If +true+, the method will
120
+ # return +Set["*"]+, if +false+, the method will raise an exception.
121
+ # @return A set of the unique allowed actions.
122
+ def authorized_actions(actor, resource, allow_wildcard: false) # rubocop:disable Metrics/MethodLength
123
+ results = query_rule('allow', actor, Polar::Variable.new('action'), resource)
124
+ actions = Set.new
125
+ results.each do |result|
126
+ action = result['action']
127
+ if action.is_a?(Polar::Variable)
128
+ return Set['*'] if allow_wildcard
129
+
130
+ raise ::Oso::Error,
131
+ 'The result of authorized_actions() contained an '\
132
+ '"unconstrained" action that could represent any '\
133
+ 'action, but allow_wildcard was set to False. To fix, '\
134
+ 'set allow_wildcard to True and compare with the "*" '\
135
+ 'string.'
136
+ end
137
+ actions.add(action)
138
+ end
139
+ actions
140
+ end
141
+
142
+ # Determine the fields of +resource+ on which +actor+ is allowed to
143
+ # perform +action+.
144
+ #
145
+ # Uses +allow_field+ rules in the policy to find all allowed fields.
146
+ #
147
+ # @param actor The actor for whom to collect allowed fields.
148
+ # @param action The action being taken on the field.
149
+ # @param resource The resource being accessed.
150
+ # @param allow_wildcard Flag to determine behavior if the policy \
151
+ # includes a wildcard field. E.g., a rule allowing any field: \
152
+ # +allow_field(_actor, _action, _resource, _field)+. If +true+, the \
153
+ # method will return +Set["*"]+, if +false+, the method will raise an \
154
+ # exception.
155
+ # @return A set of the unique allowed fields.
156
+ def authorized_fields(actor, action, resource, allow_wildcard: false) # rubocop:disable Metrics/MethodLength
157
+ results = query_rule('allow_field', actor, action, resource, Polar::Variable.new('field'))
158
+ fields = Set.new
159
+ results.each do |result|
160
+ field = result['field']
161
+ if field.is_a?(Polar::Variable)
162
+ return Set['*'] if allow_wildcard
163
+
164
+ raise ::Oso::Error,
165
+ 'The result of authorized_fields() contained an '\
166
+ '"unconstrained" field that could represent any '\
167
+ 'field, but allow_wildcard was set to False. To fix, '\
168
+ 'set allow_wildcard to True and compare with the "*" '\
169
+ 'string.'
170
+ end
171
+ fields.add(field)
172
+ end
173
+ fields
174
+ end
175
+
176
+ # Returns a query for resources of type +cls+ that +actor+ is allowed
177
+ # to perform +action+ on.
178
+ #
179
+ # @param actor The actor whose permissions to check.
180
+ # @param action The action being taken on the resource.
181
+ # @param resource_cls The resource being accessed.
182
+ #
183
+ # @returns A query for resources accessible to the actor.
184
+ def authorized_query(actor, action, resource_cls) # rubocop:disable Metrics/MethodLength
185
+ resource = Polar::Variable.new 'resource'
186
+
187
+ results = query_rule(
188
+ 'allow',
189
+ actor,
190
+ action,
191
+ resource,
192
+ bindings: { 'resource' => type_constraint(resource, resource_cls) },
193
+ accept_expression: true
194
+ )
195
+
196
+ results = results.each_with_object([]) do |result, out|
197
+ result.each do |key, val|
198
+ out.push({ 'bindings' => { key => host.to_polar(val) } })
199
+ end
200
+ end
201
+
202
+ ::Oso::Polar::DataFiltering::FilterPlan
203
+ .parse(self, results, get_class_name(resource_cls))
204
+ .build_query
205
+ end
206
+
207
+ # Returns the resources of type +resource_cls+ that +actor+ is allowed
208
+ # to perform +action+ on.
209
+ #
210
+ # @param actor The actor whose permissions to check.
211
+ # @param action The action being taken on the resource.
212
+ # @param resource_cls The resource being accessed.
213
+ #
214
+ # @returns A list of resources accessible to the actor.
215
+ def authorized_resources(actor, action, resource_cls)
216
+ q = authorized_query actor, action, resource_cls
217
+ return [] if q.nil?
218
+
219
+ host.types[get_class_name resource_cls].exec_query[q]
21
220
  end
22
221
  end
23
222
  end
@@ -7,62 +7,84 @@ module Oso
7
7
  # Represents a set of filter sequences that should allow the host
8
8
  # to obtain the records satisfying a query.
9
9
  class FilterPlan
10
- include Enumerable
11
10
  attr_reader :result_sets
12
11
 
13
- def initialize(polar, partials, class_name)
12
+ def self.parse(polar, partials, class_name)
14
13
  types = polar.host.serialize_types
15
14
  parsed_json = polar.ffi.build_filter_plan(types, partials, 'resource', class_name)
16
- @polar = polar
17
- @result_sets = parsed_json['result_sets'].map do |rset|
18
- ResultSet.new polar, rset
15
+ result_sets = parsed_json['result_sets'].map do |rset|
16
+ ResultSet.parse polar, rset
19
17
  end
18
+
19
+ new polar: polar, result_sets: result_sets
20
20
  end
21
21
 
22
- def each(&blk)
23
- result_sets.each(&blk)
22
+ def initialize(polar:, result_sets:)
23
+ @polar = polar
24
+ @result_sets = result_sets
24
25
  end
25
26
 
26
- def resolve # rubocop:disable Metrics/AbcSize
27
- reduce([]) do |acc, rs|
28
- requests = rs.requests
29
- acc + rs.resolve_order.each_with_object({}) do |i, set_results|
30
- req = requests[i]
31
- constraints = req.constraints
32
- constraints.each { |c| c.ground set_results }
33
- set_results[i] = @polar.host.types[req.class_tag].fetcher[constraints]
34
- end[rs.result_id]
35
- end.uniq
27
+ def build_query # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
28
+ combine = nil
29
+ result_sets.each_with_object([]) do |rs, qb|
30
+ rs.resolve_order.each_with_object({}) do |i, set_results|
31
+ req = rs.requests[i]
32
+ cs = req.constraints.each { |c| c.ground set_results }
33
+ typ = @polar.host.types[req.class_tag]
34
+ q = typ.build_query[cs]
35
+ if i != rs.result_id
36
+ set_results[i] = typ.exec_query[q]
37
+ else
38
+ combine = typ.combine_query
39
+ qb.push q
40
+ end
41
+ end
42
+ end.reduce(&combine)
36
43
  end
37
44
 
38
45
  # Represents a sequence of filters for one set of results
39
46
  class ResultSet
40
47
  attr_reader :requests, :resolve_order, :result_id
41
48
 
42
- def initialize(polar, parsed_json)
43
- @resolve_order = parsed_json['resolve_order']
44
- @result_id = parsed_json['result_id']
45
- @requests = parsed_json['requests'].each_with_object({}) do |req, reqs|
46
- reqs[req[0].to_i] = Request.new(polar, req[1])
49
+ def self.parse(polar, parsed_json)
50
+ resolve_order = parsed_json['resolve_order']
51
+ result_id = parsed_json['result_id']
52
+ requests = parsed_json['requests'].each_with_object({}) do |req, reqs|
53
+ reqs[req[0].to_i] = Request.parse(polar, req[1])
47
54
  end
55
+
56
+ new resolve_order: resolve_order, result_id: result_id, requests: requests
57
+ end
58
+
59
+ def initialize(requests:, resolve_order:, result_id:)
60
+ @resolve_order = resolve_order
61
+ @requests = requests
62
+ @result_id = result_id
48
63
  end
64
+ end
49
65
 
50
- # Represents a filter for a result set
51
- class Request
52
- attr_reader :constraints, :class_tag
66
+ # Represents a filter for a result set
67
+ class Request
68
+ attr_reader :constraints, :class_tag
53
69
 
54
- def initialize(polar, parsed_json)
55
- @constraints = parsed_json['constraints'].map do |con|
56
- Constraint.parse polar, con
57
- end
58
- @class_tag = parsed_json['class_tag']
70
+ def self.parse(polar, parsed_json)
71
+ constraints = parsed_json['constraints'].map do |con|
72
+ Filter.parse polar, con
59
73
  end
74
+ class_tag = parsed_json['class_tag']
75
+
76
+ new(constraints: constraints, class_tag: class_tag)
77
+ end
78
+
79
+ def initialize(constraints:, class_tag:)
80
+ @constraints = constraints
81
+ @class_tag = class_tag
60
82
  end
61
83
  end
62
84
  end
63
85
 
64
- # Represents relationships between resources, eg. parent/child
65
- class Relationship
86
+ # Represents relationships between resources, eg. one-one or one-many
87
+ class Relation
66
88
  attr_reader :kind, :other_type, :my_field, :other_field
67
89
 
68
90
  def initialize(kind:, other_type:, my_field:, other_field:)
@@ -93,12 +115,13 @@ module Oso
93
115
  end
94
116
 
95
117
  # Represents a condition that must hold on a resource.
96
- class Constraint
118
+ class Filter
97
119
  attr_reader :kind, :field, :value
98
120
 
99
121
  CHECKS = {
100
122
  'Eq' => ->(a, b) { a == b },
101
123
  'In' => ->(a, b) { b.include? a },
124
+ 'Neq' => ->(a, b) { a != b },
102
125
  'Contains' => ->(a, b) { a.include? b }
103
126
  }.freeze
104
127
 
@@ -126,8 +149,6 @@ module Oso
126
149
 
127
150
  def self.parse(polar, constraint) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
128
151
  kind = constraint['kind']
129
- raise unless %w[Eq In Contains].include? kind
130
-
131
152
  field = constraint['field']
132
153
  value = constraint['value']
133
154
 
@@ -3,7 +3,7 @@
3
3
  module Oso
4
4
  module Polar
5
5
  # Base error type for Oso::Polar.
6
- class Error < ::RuntimeError
6
+ class Error < ::Oso::Error
7
7
  attr_reader :stack_trace
8
8
 
9
9
  # @param message [String]
@@ -68,7 +68,7 @@ module Oso
68
68
  end
69
69
  end
70
70
  class DuplicateClassAliasError < PolarRuntimeError # rubocop:disable Style/Documentation
71
- # @param as [String]
71
+ # @param name [String]
72
72
  # @param old [Class]
73
73
  # @param new [Class]
74
74
  def initialize(name:, old:, new:)
@@ -101,7 +101,6 @@ module Oso
101
101
  class ParameterError < ApiError; end
102
102
 
103
103
  class ValidationError < Error; end
104
- class RolesValidationError < Error; end
105
104
 
106
105
  UNEXPECTED_EXPRESSION_MESSAGE = <<~MSG
107
106
  Received Expression from Polar VM. The Expression type is not yet supported in this language.
@@ -56,7 +56,7 @@ module Oso
56
56
  operational_error(subkind, msg: msg, details: details)
57
57
  when 'Parameter'
58
58
  api_error(subkind, msg: msg, details: details)
59
- when 'RolesValidation'
59
+ when 'Validation'
60
60
  validation_error(msg, details: details)
61
61
  end
62
62
  end
@@ -146,7 +146,7 @@ module Oso
146
146
  # @return [::Oso::Polar::ValidationError] the object converted into the expected format.
147
147
  private_class_method def self.validation_error(msg, details:)
148
148
  # This is currently the only type of validation error.
149
- ::Oso::Polar::RolesValidationError.new(msg, details: details)
149
+ ::Oso::Polar::ValidationError.new(msg, details: details)
150
150
  end
151
151
  end
152
152
  end
@@ -14,15 +14,14 @@ module Oso
14
14
  ffi_lib FFI::LIB_PATH
15
15
 
16
16
  attach_function :new, :polar_new, [], FFI::Polar
17
- attach_function :enable_roles, :polar_enable_roles, [FFI::Polar], :int32
18
- attach_function :validate_roles_config, :polar_validate_roles_config, [FFI::Polar, :string], :int32
19
- attach_function :load, :polar_load, [FFI::Polar, :string, :string], :int32
17
+ attach_function :load, :polar_load, [FFI::Polar, :string], :int32
20
18
  attach_function :clear_rules, :polar_clear_rules, [FFI::Polar], :int32
21
19
  attach_function :next_inline_query, :polar_next_inline_query, [FFI::Polar, :uint32], FFI::Query
22
20
  attach_function :new_id, :polar_get_external_id, [FFI::Polar], :uint64
23
21
  attach_function :new_query_from_str, :polar_new_query, [FFI::Polar, :string, :uint32], FFI::Query
24
22
  attach_function :new_query_from_term, :polar_new_query_from_term, [FFI::Polar, :string, :uint32], FFI::Query
25
23
  attach_function :register_constant, :polar_register_constant, [FFI::Polar, :string, :string], :int32
24
+ attach_function :register_mro, :polar_register_mro, [FFI::Polar, :string, :string], :int32
26
25
  attach_function :next_message, :polar_next_polar_message, [FFI::Polar], FFI::Message
27
26
  attach_function :free, :polar_free, [FFI::Polar], :int32
28
27
  attach_function(
@@ -43,20 +42,6 @@ module Oso
43
42
  polar
44
43
  end
45
44
 
46
- # @raise [FFI::Error] if the FFI call returns an error.
47
- def enable_roles
48
- result = Rust.enable_roles(self)
49
- process_messages
50
- handle_error if result.zero?
51
- end
52
-
53
- # @raise [FFI::Error] if the FFI call returns an error.
54
- def validate_roles_config(config)
55
- result = Rust.validate_roles_config(self, JSON.dump(config))
56
- process_messages
57
- handle_error if result.zero?
58
- end
59
-
60
45
  def build_filter_plan(types, partials, variable, class_tag)
61
46
  types = JSON.dump(types)
62
47
  partials = JSON.dump(partials)
@@ -67,11 +52,10 @@ module Oso
67
52
  JSON.parse plan
68
53
  end
69
54
 
70
- # @param src [String]
71
- # @param filename [String]
55
+ # @param sources [Array<Source>]
72
56
  # @raise [FFI::Error] if the FFI call returns an error.
73
- def load(src, filename: nil)
74
- loaded = Rust.load(self, src, filename)
57
+ def load(sources)
58
+ loaded = Rust.load(self, JSON.dump(sources))
75
59
  process_messages
76
60
  handle_error if loaded.zero?
77
61
  end
@@ -133,6 +117,14 @@ module Oso
133
117
  handle_error if registered.zero?
134
118
  end
135
119
 
120
+ # @param name [String]
121
+ # @param mro [Array<Integer>]
122
+ # @raise [FFI::Error] if the FFI call returns an error.
123
+ def register_mro(name, mro)
124
+ registered = Rust.register_mro(self, name, JSON.dump(mro))
125
+ handle_error if registered.zero?
126
+ end
127
+
136
128
  def next_message
137
129
  Rust.next_message(self)
138
130
  end
@@ -50,8 +50,7 @@ module Oso
50
50
  handle_error if res.zero?
51
51
  end
52
52
 
53
- # @param result [Boolean]
54
- # @param call_id [Integer]
53
+ # @param message [String]
55
54
  # @raise [FFI::Error] if the FFI call returns an error.
56
55
  def application_error(message)
57
56
  res = Rust.application_error(self, message)
@@ -36,21 +36,23 @@ module Oso
36
36
 
37
37
  # For holding type metadata: name, fields, etc.
38
38
  class UserType
39
- attr_reader :name, :klass, :id, :fields, :fetcher
39
+ attr_reader :name, :klass, :id, :fields, :build_query, :combine_query, :exec_query
40
40
 
41
- def initialize(name:, klass:, id:, fields:, fetcher:)
41
+ def initialize(name:, klass:, id:, fields:, build_query:, combine_query:, exec_query:) # rubocop:disable Metrics/ParameterLists
42
42
  @name = name
43
43
  @klass = klass
44
44
  @id = id
45
45
  # accept symbol keys
46
46
  @fields = fields.each_with_object({}) { |kv, o| o[kv[0].to_s] = kv[1] }
47
- @fetcher = fetcher
47
+ @build_query = build_query
48
+ @combine_query = combine_query
49
+ @exec_query = exec_query
48
50
  end
49
51
  end
50
52
 
51
53
  # Translate between Polar and the host language (Ruby).
52
54
  class Host # rubocop:disable Metrics/ClassLength
53
- # @return [Hash<String, Class>]
55
+ # @return [Hash<String, UserType>]
54
56
  attr_reader :types
55
57
 
56
58
  protected
@@ -97,19 +99,31 @@ module Oso
97
99
  # @return [String] the name the class is cached as.
98
100
  # @raise [DuplicateClassAliasError] if attempting to register a class
99
101
  # under a previously-registered name.
100
- def cache_class(cls, name:, fields: {}, fetcher: nil)
102
+ def cache_class(cls, name:, fields:, build_query:, combine_query:, exec_query:) # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
101
103
  raise DuplicateClassAliasError.new name: name, old: get_class(name), new: cls if types.key? name
102
104
 
103
105
  types[name] = types[cls] = UserType.new(
104
106
  name: name,
105
107
  klass: PolarClass.new(cls),
106
108
  id: cache_instance(cls),
107
- fields: fields,
108
- fetcher: fetcher
109
+ fields: fields || {},
110
+ combine_query: combine_query,
111
+ exec_query: exec_query,
112
+ build_query: build_query
109
113
  )
110
114
  name
111
115
  end
112
116
 
117
+ def register_mros # rubocop:disable Metrics/AbcSize
118
+ types.values.uniq.each do |typ|
119
+ mro = []
120
+ typ.klass.get.ancestors.each do |a|
121
+ mro.push(types[a].id) if types.key?(a)
122
+ end
123
+ ffi_polar.register_mro(typ.name, mro)
124
+ end
125
+ end
126
+
113
127
  # Check if an instance exists in the {#instances} cache.
114
128
  #
115
129
  # @param id [Integer]
@@ -181,7 +195,7 @@ module Oso
181
195
 
182
196
  # Compare two values
183
197
  #
184
- # @param op [String] operation to perform.
198
+ # @param operation [String] operation to perform.
185
199
  # @param args [Array<Object>] left and right args to operation.
186
200
  # @raise [PolarRuntimeError] if operation fails or is unsupported.
187
201
  # @return [Boolean]
@@ -234,9 +248,9 @@ module Oso
234
248
  field_types = {}
235
249
  fields.each do |k, v|
236
250
  field_types[k] =
237
- if v.is_a? ::Oso::Polar::DataFiltering::Relationship
251
+ if v.is_a? ::Oso::Polar::DataFiltering::Relation
238
252
  {
239
- 'Relationship' => {
253
+ 'Relation' => {
240
254
  'kind' => v.kind,
241
255
  'other_class_tag' => v.other_type,
242
256
  'my_field' => v.my_field,
@@ -293,7 +307,9 @@ module Oso
293
307
  { 'Pattern' => { 'Instance' => { 'tag' => value.tag, 'fields' => dict['Dictionary'] } } }
294
308
  end
295
309
  else
296
- { 'ExternalInstance' => { 'instance_id' => cache_instance(value), 'repr' => nil } }
310
+ instance_id = nil
311
+ instance_id = types[value].id if value.is_a?(Class) && types.key?(value)
312
+ { 'ExternalInstance' => { 'instance_id' => cache_instance(value, id: instance_id), 'repr' => nil } }
297
313
  end
298
314
  { 'value' => value }
299
315
  end
@@ -27,6 +27,35 @@ def print_error(error)
27
27
  warn error.message
28
28
  end
29
29
 
30
+ # Polar source string with optional filename.
31
+ class Source
32
+ # @return [String]
33
+ attr_reader :src, :filename
34
+
35
+ # @param src [String]
36
+ # @param filename [String]
37
+ def initialize(src, filename: nil)
38
+ @src = src
39
+ @filename = filename
40
+ end
41
+
42
+ def to_json(*_args)
43
+ { src: src, filename: filename }.to_json
44
+ end
45
+ end
46
+
47
+ def filename_to_source(filename)
48
+ raise Oso::Polar::PolarFileExtensionError, filename unless File.extname(filename) == '.polar'
49
+
50
+ src = File.open(filename, &:read)
51
+
52
+ raise Oso::Polar::NullByteInPolarFileError if src.chomp("\0").include?("\0")
53
+
54
+ Source.new(src, filename: filename)
55
+ rescue Errno::ENOENT
56
+ raise Oso::Polar::PolarFileNotFoundError, filename
57
+ end
58
+
30
59
  module Oso
31
60
  module Polar
32
61
  # Create and manage an instance of the Polar runtime.
@@ -34,11 +63,10 @@ module Oso
34
63
  # @return [Host]
35
64
  attr_reader :host
36
65
 
37
- def initialize # rubocop:disable Metrics/MethodLength
66
+ def initialize
38
67
  @ffi_polar = FFI::Polar.create
39
68
  @host = Host.new(ffi_polar)
40
69
  @ffi_polar.enrich_message = @host.method(:enrich_message)
41
- @polar_roles_enabled = false
42
70
 
43
71
  # Register global constants.
44
72
  register_constant nil, name: 'nil'
@@ -56,42 +84,6 @@ module Oso
56
84
  @ffi_polar
57
85
  end
58
86
 
59
- def enable_roles # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
60
- return if polar_roles_enabled
61
-
62
- roles_helper = Class.new do
63
- def self.join(separator, left, right)
64
- [left, right].join(separator)
65
- end
66
- end
67
- register_constant(roles_helper, name: '__oso_internal_roles_helpers__')
68
- ffi_polar.enable_roles
69
- self.polar_roles_enabled = true
70
-
71
- # validate config
72
- validation_query_results = []
73
- loop do
74
- query = ffi_polar.next_inline_query
75
- break if query.nil?
76
-
77
- new_host = host.dup
78
- new_host.accept_expression = true
79
- results = Query.new(query, host: new_host).to_a
80
- raise InlineQueryFailedError, query.source if results.empty?
81
-
82
- validation_query_results.push results
83
- end
84
-
85
- # turn bindings back into polar
86
- validation_query_results = validation_query_results.map do |results|
87
- results.map do |result|
88
- { 'bindings' => result.transform_values { |v| host.to_polar(v) } }
89
- end
90
- end
91
-
92
- ffi_polar.validate_roles_config(validation_query_results)
93
- end
94
-
95
87
  # get the (maybe user-supplied) name of a class.
96
88
  # kind of a hack because of class autoreloading.
97
89
  def get_class_name(klass) # rubocop:disable Metrics/AbcSize
@@ -108,61 +100,52 @@ module Oso
108
100
  end
109
101
  end
110
102
 
111
- def get_allowed_resources(actor, action, klass) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
112
- resource = Variable.new 'resource'
113
- class_name = get_class_name klass
114
- constraint = Expression.new(
115
- 'And',
116
- [Expression.new('Isa', [resource, Pattern.new(class_name, {})])]
117
- )
118
-
119
- results = query_rule(
120
- 'allow',
121
- actor,
122
- action,
123
- resource,
124
- bindings: { 'resource' => constraint },
125
- accept_expression: true
126
- )
127
-
128
- complete = []
129
- partial = []
130
-
131
- results.to_a.each do |result|
132
- result.to_a.each do |key, val|
133
- if val.is_a? Expression
134
- partial.push({ 'bindings' => { key => host.to_polar(val) } })
135
- else
136
- complete.push val
137
- end
138
- end
139
- end
140
- filter = ::Oso::Polar::DataFiltering::FilterPlan.new(self, partial, class_name)
141
- complete + filter.resolve
142
- end
143
-
144
103
  # Clear all rules and rule sources from the current Polar instance
145
104
  #
146
105
  # @return [self] for chaining.
147
106
  def clear_rules
148
107
  ffi_polar.clear_rules
149
- ffi_polar.enable_roles if polar_roles_enabled
150
108
  self
151
109
  end
152
110
 
153
- # Load a Polar policy file.
111
+ # Load Polar policy files.
154
112
  #
155
- # @param name [String]
156
- # @raise [PolarFileExtensionError] if provided filename has invalid extension.
157
- # @raise [PolarFileNotFoundError] if provided filename does not exist.
113
+ # @param filenames [Array<String>]
114
+ # @raise [PolarFileExtensionError] if any filename has an invalid extension.
115
+ # @raise [PolarFileNotFoundError] if any filename does not exist.
116
+ # @raise [NullByteInPolarFileError] if any file contains a non-terminating null byte.
117
+ # @raise [Error] if any of the FFI calls raise one.
118
+ # @raise [InlineQueryFailedError] on the first failed inline query.
158
119
  # @return [self] for chaining.
159
- def load_file(name)
160
- raise PolarFileExtensionError, name unless File.extname(name) == '.polar'
120
+ def load_files(filenames = [])
121
+ return if filenames.empty?
161
122
 
162
- file_data = File.open(name, &:read)
163
- load_str(file_data, filename: name)
164
- rescue Errno::ENOENT
165
- raise PolarFileNotFoundError, name
123
+ sources = filenames.map { |f| filename_to_source f }
124
+ load_sources(sources)
125
+ self
126
+ end
127
+
128
+ # Load a Polar policy file.
129
+ #
130
+ # @param filename [String]
131
+ # @raise [PolarFileExtensionError] if filename has an invalid extension.
132
+ # @raise [PolarFileNotFoundError] if filename does not exist.
133
+ # @raise [NullByteInPolarFileError] if file contains a non-terminating null byte.
134
+ # @raise [Error] if any of the FFI calls raise one.
135
+ # @raise [InlineQueryFailedError] on the first failed inline query.
136
+ # @return [self] for chaining.
137
+ #
138
+ # @deprecated {#load_file} has been deprecated in favor of {#load_files}
139
+ # as of the 0.20.0 release. Please see changelog for migration
140
+ # instructions:
141
+ # https://docs.osohq.com/project/changelogs/2021-09-15.html
142
+ def load_file(filename)
143
+ warn <<~WARNING
144
+ `Oso#load_file` has been deprecated in favor of `Oso#load_files` as of the 0.20.0 release.
145
+
146
+ Please see changelog for migration instructions: https://docs.osohq.com/project/changelogs/2021-09-15.html
147
+ WARNING
148
+ load_files([filename])
166
149
  end
167
150
 
168
151
  # Load a Polar string into the KB.
@@ -170,26 +153,13 @@ module Oso
170
153
  # @param str [String] Polar string to load.
171
154
  # @param filename [String] Name of Polar source file.
172
155
  # @raise [NullByteInPolarFileError] if str includes a non-terminating null byte.
173
- # @raise [InlineQueryFailedError] on the first failed inline query.
174
156
  # @raise [Error] if any of the FFI calls raise one.
157
+ # @raise [InlineQueryFailedError] on the first failed inline query.
175
158
  # @return [self] for chaining.
176
- def load_str(str, filename: nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
159
+ def load_str(str, filename: nil)
177
160
  raise NullByteInPolarFileError if str.chomp("\0").include?("\0")
178
161
 
179
- ffi_polar.load(str, filename: filename)
180
- loop do
181
- next_query = ffi_polar.next_inline_query
182
- break if next_query.nil?
183
-
184
- raise InlineQueryFailedError, next_query.source if Query.new(next_query, host: host).first.nil?
185
- end
186
-
187
- # If roles are enabled, re-validate config when new rules are loaded.
188
- if polar_roles_enabled
189
- self.polar_roles_enabled = false
190
- enable_roles
191
- end
192
-
162
+ load_sources([Source.new(str, filename: filename)])
193
163
  self
194
164
  end
195
165
 
@@ -227,6 +197,16 @@ module Oso
227
197
  query(Predicate.new(name, args: args), host: host, bindings: bindings)
228
198
  end
229
199
 
200
+ # Query for a rule, returning true if it has any results.
201
+ #
202
+ # @param name [String]
203
+ # @param args [Array<Object>]
204
+ # @return [Boolean] indicating whether the query found at least one result.
205
+ # @raise [Error] if the FFI call raises one.
206
+ def query_rule_once(name, *args)
207
+ query_rule(name, *args).any?
208
+ end
209
+
230
210
  # Register a Ruby class with Polar.
231
211
  #
232
212
  # @param cls [Class] the class to register.
@@ -235,8 +215,15 @@ module Oso
235
215
  # under a previously-registered name.
236
216
  # @raise [FFI::Error] if the FFI call returns an error.
237
217
  # @return [self] for chaining.
238
- def register_class(cls, name: nil, fields: {}, fetcher: nil)
239
- name = host.cache_class(cls, name: name || cls.name, fields: fields, fetcher: fetcher)
218
+ def register_class(cls, name: nil, fields: nil, combine_query: nil, build_query: nil, exec_query: nil) # rubocop:disable Metrics/ParameterLists
219
+ name = host.cache_class(
220
+ cls,
221
+ name: name || cls.name,
222
+ fields: fields,
223
+ build_query: build_query || maybe_mtd(cls, :build_query),
224
+ combine_query: combine_query || maybe_mtd(cls, :combine_query),
225
+ exec_query: exec_query || maybe_mtd(cls, :exec_query)
226
+ )
240
227
  register_constant(cls, name: name)
241
228
  end
242
229
 
@@ -256,7 +243,7 @@ module Oso
256
243
  # @param files [Array<String>]
257
244
  # @raise [Error] if the FFI call raises one.
258
245
  def repl(files = [])
259
- files.map { |f| load_file(f) }
246
+ load_files(files)
260
247
  prompt = "#{FG_BLUE}query>#{RESET} "
261
248
  # Try loading the readline module from the Ruby stdlib. If we get a
262
249
  # LoadError, fall back to the standard REPL with no readline support.
@@ -268,9 +255,36 @@ module Oso
268
255
 
269
256
  private
270
257
 
258
+ def type_constraint(var, cls)
259
+ Expression.new(
260
+ 'And',
261
+ [Expression.new('Isa', [var, Pattern.new(get_class_name(cls), {})])]
262
+ )
263
+ end
264
+
265
+ def maybe_mtd(cls, mtd)
266
+ cls.respond_to?(mtd) && cls.method(mtd) || nil
267
+ end
268
+
271
269
  # @return [FFI::Polar]
272
270
  attr_reader :ffi_polar
273
- attr_accessor :polar_roles_enabled
271
+
272
+ # Register MROs, load Polar code, and check inline queries.
273
+ # @param sources [Array<Source>] Polar sources to load.
274
+ def load_sources(sources)
275
+ host.register_mros
276
+ ffi_polar.load(sources)
277
+ check_inline_queries
278
+ end
279
+
280
+ def check_inline_queries
281
+ loop do
282
+ next_query = ffi_polar.next_inline_query
283
+ break if next_query.nil?
284
+
285
+ raise InlineQueryFailedError, next_query.source if Query.new(next_query, host: host).none?
286
+ end
287
+ end
274
288
 
275
289
  # The R and L in REPL for systems where readline is available.
276
290
  def repl_readline(prompt)
@@ -18,6 +18,16 @@ module Oso
18
18
  bindings.each { |k, v| ffi_query.bind k, host.to_polar(v) }
19
19
  end
20
20
 
21
+ # Create an enumerator that can be polled to advance the query loop. Yields
22
+ # results one by one.
23
+ #
24
+ # @yieldparam [Hash<String, Object>]
25
+ # @return [Enumerator]
26
+ # @raise [Error] if any of the FFI calls raise one.
27
+ def each(&block)
28
+ run(&block)
29
+ end
30
+
21
31
  private
22
32
 
23
33
  # @return [Hash<Integer, Enumerator>]
@@ -65,10 +75,11 @@ module Oso
65
75
  # Fetch the next result from calling a Ruby method and prepare it for
66
76
  # transmission across the FFI boundary.
67
77
  #
68
- # @param method [#to_sym]
69
- # @param args [Array<Hash>]
78
+ # @param attribute [#to_sym]
70
79
  # @param call_id [Integer]
71
80
  # @param instance [Hash<String, Object>]
81
+ # @param args [Array<Hash>]
82
+ # @param kwargs [Hash<String, Object>]
72
83
  # @raise [Error] if the FFI call raises one.
73
84
  def handle_call(attribute, call_id:, instance:, args:, kwargs:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
74
85
  instance = host.to_ruby(instance)
@@ -100,12 +111,12 @@ module Oso
100
111
  raise unless cls.fields.key? tag
101
112
 
102
113
  ref = cls.fields[tag]
103
- return host.types[ref] unless ref.is_a? ::Oso::Polar::DataFiltering::Relationship
114
+ return host.types[ref] unless ref.is_a? ::Oso::Polar::DataFiltering::Relation
104
115
 
105
116
  case ref.kind
106
- when 'parent'
117
+ when 'one'
107
118
  host.types[ref.other_type]
108
- when 'children'
119
+ when 'many'
109
120
  host.types[Array]
110
121
  end
111
122
  end
@@ -152,12 +163,12 @@ module Oso
152
163
  host.make_instance(cls_name, args: args, kwargs: kwargs, id: id)
153
164
  end
154
165
 
155
- # Create a generator that can be polled to advance the query loop.
166
+ # Run the main Polar loop, yielding results as they are emitted from the VM.
156
167
  #
157
168
  # @yieldparam [Hash<String, Object>]
158
169
  # @return [Enumerator]
159
170
  # @raise [Error] if any of the FFI calls raise one.
160
- def each # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
171
+ def run # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
161
172
  loop do # rubocop:disable Metrics/BlockLength
162
173
  event = ffi_query.next_event
163
174
  case event.kind
@@ -227,21 +238,21 @@ module Oso
227
238
  return unless typ
228
239
 
229
240
  rel = typ.fields[attr]
230
- return unless rel.is_a? ::Oso::Polar::DataFiltering::Relationship
241
+ return unless rel.is_a? ::Oso::Polar::DataFiltering::Relation
231
242
 
232
243
  rel
233
244
  end
234
245
 
235
246
  def handle_relationship(call_id, instance, rel) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
236
- fetcher = host.types[rel.other_type].fetcher
237
- constraint = ::Oso::Polar::DataFiltering::Constraint.new(
247
+ typ = host.types[rel.other_type]
248
+ constraint = ::Oso::Polar::DataFiltering::Filter.new(
238
249
  kind: 'Eq',
239
250
  field: rel.other_field,
240
251
  value: instance.send(rel.my_field)
241
252
  )
242
- res = fetcher[[constraint]].uniq
253
+ res = typ.exec_query[typ.build_query[[constraint]]]
243
254
 
244
- if rel.kind == 'parent'
255
+ if rel.kind == 'one'
245
256
  raise "multiple parents: #{res}" unless res.length == 1
246
257
 
247
258
  res = res[0]
data/lib/oso/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Oso
4
- VERSION = '0.20.0-beta'
4
+ VERSION = '0.20.1-beta'
5
5
  end
data/lib/oso.rb CHANGED
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'oso/oso'
4
+ require 'oso/errors'
4
5
  require 'oso/polar'
5
6
  require 'oso/version'
6
7
 
7
- # Top-level namespace for oso authorization library.
8
+ # Top-level namespace for Oso authorization library.
8
9
  module Oso
9
- def self.new
10
- ::Oso::Oso.new
10
+ def self.new(not_found_error: NotFoundError, forbidden_error: ForbiddenError, read_action: 'read')
11
+ ::Oso::Oso.new(not_found_error: not_found_error, forbidden_error: forbidden_error, read_action: read_action)
11
12
  end
12
13
  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.20.0.pre.beta
4
+ version: 0.20.1.pre.beta
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oso Security, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-24 00:00:00.000000000 Z
11
+ date: 2021-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -158,6 +158,7 @@ files:
158
158
  - ext/oso-oso/lib/libpolar.so
159
159
  - ext/oso-oso/lib/polar.dll
160
160
  - lib/oso.rb
161
+ - lib/oso/errors.rb
161
162
  - lib/oso/oso.rb
162
163
  - lib/oso/polar.rb
163
164
  - lib/oso/polar/data_filtering.rb