oso-oso 0.15.0 → 0.20.1

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: 12fca0670b29961bafc90ea1565f5ef2e501a778
4
- data.tar.gz: ca3fee786141d350b6d79e67ab042e650a082566
3
+ metadata.gz: da642b9fbbfcad2d836a8db8c6ac83cbc8f1127a
4
+ data.tar.gz: 1012ef61aa0a1791a1c759ba9f62e108918898b6
5
5
  SHA512:
6
- metadata.gz: edcc9dd56153cc927d2791dfe398264c538030f44e7a1e36e9157b37bacc05033ad31f24d6d0c0f95b71a8de166633bcd24755e94fcb90683fee70a02711d0c2
7
- data.tar.gz: e3155aef370a1d61c388f0342ee5a692afad0a473a362e740e1f8124afc86d3d18ae080e7acac856b081b7fc7e27d50b56b5e273d51e7cc1359092896480e92b
6
+ metadata.gz: 48d52111b817769f31086b4adbd7252bcab4355792ba85e378ab4a27c68272e8d2af55f8d2a258e0a3bb3b2cea30b6d0c98f6e20eec7ed9d1b127011a506cab7
7
+ data.tar.gz: f40a6c5565655ee508ebc0c20b7f571616b42d4a76f986b51ad4349c1fe05200d8165990c98a00e2a7388ac41eb96182095b52a9f08ad022cc0880801ad87997
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  vendor
10
+ active_record_test.db
10
11
 
11
12
  # rspec failure tracking
12
13
  .rspec_status
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,28 +1,44 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- oso-oso (0.15.0)
4
+ oso-oso (0.20.1)
5
5
  ffi (~> 1.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- ast (2.4.1)
11
- backport (1.1.2)
12
- benchmark (0.1.0)
10
+ activemodel (5.2.6)
11
+ activesupport (= 5.2.6)
12
+ activerecord (5.2.6)
13
+ activemodel (= 5.2.6)
14
+ activesupport (= 5.2.6)
15
+ arel (>= 9.0)
16
+ activesupport (5.2.6)
17
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
+ i18n (>= 0.7, < 2)
19
+ minitest (~> 5.1)
20
+ tzinfo (~> 1.1)
21
+ arel (9.0.0)
22
+ ast (2.4.2)
23
+ backport (1.2.0)
24
+ benchmark (0.1.1)
13
25
  byebug (11.1.3)
14
26
  coderay (1.1.3)
27
+ concurrent-ruby (1.1.9)
15
28
  diff-lcs (1.4.4)
16
29
  e2mmap (0.1.0)
17
- ffi (1.15.1)
30
+ ffi (1.15.4)
31
+ i18n (1.8.10)
32
+ concurrent-ruby (~> 1.0)
18
33
  jaro_winkler (1.5.4)
19
34
  maruku (0.7.3)
20
35
  method_source (1.0.0)
21
36
  mini_portile2 (2.4.0)
37
+ minitest (5.14.4)
22
38
  nokogiri (1.10.10)
23
39
  mini_portile2 (~> 2.4.0)
24
- parallel (1.19.2)
25
- parser (2.7.1.4)
40
+ parallel (1.20.1)
41
+ parser (2.7.2.0)
26
42
  ast (~> 2.4.1)
27
43
  pry (0.13.1)
28
44
  coderay (~> 1.1)
@@ -32,23 +48,23 @@ GEM
32
48
  pry (~> 0.13.0)
33
49
  rainbow (3.0.0)
34
50
  rake (12.3.3)
35
- regexp_parser (1.7.1)
51
+ regexp_parser (2.1.1)
36
52
  reverse_markdown (2.0.0)
37
53
  nokogiri
38
- rexml (3.2.4)
39
- rspec (3.9.0)
40
- rspec-core (~> 3.9.0)
41
- rspec-expectations (~> 3.9.0)
42
- rspec-mocks (~> 3.9.0)
43
- rspec-core (3.9.2)
44
- rspec-support (~> 3.9.3)
45
- 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)
46
62
  diff-lcs (>= 1.2.0, < 2.0)
47
- rspec-support (~> 3.9.0)
48
- rspec-mocks (3.9.1)
63
+ rspec-support (~> 3.10.0)
64
+ rspec-mocks (3.10.2)
49
65
  diff-lcs (>= 1.2.0, < 2.0)
50
- rspec-support (~> 3.9.0)
51
- rspec-support (3.9.3)
66
+ rspec-support (~> 3.10.0)
67
+ rspec-support (3.10.2)
52
68
  rubocop (0.89.1)
53
69
  parallel (~> 1.10)
54
70
  parser (>= 2.7.1.1)
@@ -58,10 +74,10 @@ GEM
58
74
  rubocop-ast (>= 0.3.0, < 1.0)
59
75
  ruby-progressbar (~> 1.7)
60
76
  unicode-display_width (>= 1.4.0, < 2.0)
61
- rubocop-ast (0.3.0)
62
- parser (>= 2.7.1.4)
63
- ruby-progressbar (1.10.1)
64
- 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)
65
81
  backport (~> 1.1)
66
82
  benchmark
67
83
  bundler (>= 1.17.2)
@@ -75,21 +91,28 @@ GEM
75
91
  thor (~> 1.0)
76
92
  tilt (~> 2.0)
77
93
  yard (~> 0.9, >= 0.9.24)
78
- thor (1.0.1)
94
+ sqlite3 (1.4.2)
95
+ thor (1.1.0)
96
+ thread_safe (0.3.6)
79
97
  tilt (2.0.10)
98
+ tzinfo (1.2.9)
99
+ thread_safe (~> 0.1)
80
100
  unicode-display_width (1.7.0)
81
- yard (0.9.25)
101
+ yard (0.9.26)
82
102
 
83
103
  PLATFORMS
84
104
  ruby
105
+ x86_64-darwin-20
85
106
 
86
107
  DEPENDENCIES
108
+ activerecord
87
109
  oso-oso!
88
110
  pry-byebug (~> 3.9.0)
89
111
  rake (~> 12.0)
90
112
  rspec (~> 3.0)
91
113
  rubocop (~> 0.89.1)
92
114
  solargraph (~> 0.39.14)
115
+ sqlite3
93
116
  yard (~> 0.9.25)
94
117
 
95
118
  BUNDLED WITH
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
+ # Create a query for resources of type +cls+ that +actor+ is
177
+ # allowed 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
+ # @return 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
+ # Determine the resources of type +resource_cls+ that +actor+
208
+ # is allowed 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
+ # @return 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