critic 0.2.2 → 0.2.3
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 +4 -4
- data/.travis.yml +4 -0
- data/CHANGELOG.md +23 -2
- data/LICENSE.txt +18 -4
- data/README.md +204 -4
- data/gemfiles/activesupport_5_2.gemfile +13 -0
- data/gemfiles/activesupport_5_2.gemfile.lock +78 -0
- data/lib/critic/callbacks.rb +6 -2
- data/lib/critic/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f826726f9d8ac8cc997b05fdbc56a8ccc1b5a60
|
4
|
+
data.tar.gz: 18a60cef6bfc4fbb47b9e0abd600663caae9b8cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4990660d3ea2c6e66d97c27fe5cd5f2014da4ef206783cc4b0203eb07454c9b94d0c4838797465ea9dab0b6a9f4b31469853b9752ba5efa348e41791edc32758
|
7
|
+
data.tar.gz: 9167566dd54f8224645edcc09925466f39b35d5c5d9e3cb0a6a5771d2813a931af24067fd4c448a9dfe9b237eaf442169d4cb87e39ba55ded176c3579c66623b
|
data/.travis.yml
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
language: ruby
|
2
2
|
sudo: false
|
3
|
+
before_install:
|
4
|
+
- gem install -v 1.13.6 bundler --no-document
|
5
|
+
- gem install -v 1.17.3 bundler --no-document
|
3
6
|
cache:
|
4
7
|
- bundler
|
5
8
|
rvm:
|
@@ -10,6 +13,7 @@ script:
|
|
10
13
|
notifications:
|
11
14
|
email: false
|
12
15
|
gemfile:
|
16
|
+
- gemfiles/activesupport_5_2.gemfile
|
13
17
|
- gemfiles/activesupport_5.gemfile
|
14
18
|
- gemfiles/activesupport_4.gemfile
|
15
19
|
- gemfiles/activesupport_3.gemfile
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,23 @@
|
|
1
|
-
#
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [Unreleased](https://github.com/lanej/critic/tree/HEAD)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/lanej/critic/compare/v0.2.2...HEAD)
|
6
|
+
|
7
|
+
**Merged pull requests:**
|
8
|
+
|
9
|
+
- Fix for ActiveSupport 5.2 [\#5](https://github.com/lanej/critic/pull/5) ([akappen](https://github.com/akappen))
|
10
|
+
|
11
|
+
## [v0.2.2](https://github.com/lanej/critic/tree/v0.2.2) (2017-01-04)
|
12
|
+
|
13
|
+
[Full Changelog](https://github.com/lanej/critic/compare/v0.2.1...v0.2.2)
|
14
|
+
|
15
|
+
**Merged pull requests:**
|
16
|
+
|
17
|
+
- Fix ActiveRecord 5 support [\#4](https://github.com/lanej/critic/pull/4) ([lanej](https://github.com/lanej))
|
2
18
|
|
3
19
|
## [v0.2.1](https://github.com/lanej/critic/tree/v0.2.1) (2016-12-28)
|
20
|
+
|
4
21
|
[Full Changelog](https://github.com/lanej/critic/compare/v0.2.0...v0.2.1)
|
5
22
|
|
6
23
|
**Merged pull requests:**
|
@@ -9,6 +26,7 @@
|
|
9
26
|
- add MIT license [\#2](https://github.com/lanej/critic/pull/2) ([thommahoney](https://github.com/thommahoney))
|
10
27
|
|
11
28
|
## [v0.2.0](https://github.com/lanej/critic/tree/v0.2.0) (2016-06-22)
|
29
|
+
|
12
30
|
[Full Changelog](https://github.com/lanej/critic/compare/v0.1.1...v0.2.0)
|
13
31
|
|
14
32
|
**Merged pull requests:**
|
@@ -17,5 +35,8 @@
|
|
17
35
|
|
18
36
|
## [v0.1.1](https://github.com/lanej/critic/tree/v0.1.1) (2016-06-07)
|
19
37
|
|
38
|
+
[Full Changelog](https://github.com/lanej/critic/compare/d8ca29b513cae27a858aec24862372aa1fa02bd5...v0.1.1)
|
39
|
+
|
40
|
+
|
20
41
|
|
21
|
-
\* *This
|
42
|
+
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
data/LICENSE.txt
CHANGED
@@ -1,7 +1,21 @@
|
|
1
|
-
|
1
|
+
MIT License
|
2
2
|
|
3
|
-
|
3
|
+
Copyright (c) 2017 Josh Lane
|
4
4
|
|
5
|
-
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
6
11
|
|
7
|
-
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -36,6 +36,27 @@ There are two types of methods:
|
|
36
36
|
* *action* - determines if subject is authorized to perform a specific operation on the resource
|
37
37
|
* *scope* - returns a list of resources available to the subject
|
38
38
|
|
39
|
+
The default scope is `index` but it can be overridden by specifying `.scope`.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
# app/policies/post_policy.rb
|
43
|
+
class PostPolicy
|
44
|
+
include Critic::Policy
|
45
|
+
|
46
|
+
# set default scope
|
47
|
+
self.scope = :author_index
|
48
|
+
|
49
|
+
# now default scope
|
50
|
+
def author_index
|
51
|
+
resource.where(author_id: subject.id)
|
52
|
+
end
|
53
|
+
|
54
|
+
# no longer the default scope
|
55
|
+
def index
|
56
|
+
resource.order(:created_at)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
```
|
39
60
|
|
40
61
|
#### Actions
|
41
62
|
|
@@ -47,7 +68,8 @@ class PostPolicy
|
|
47
68
|
include Critic::Policy
|
48
69
|
|
49
70
|
def update?
|
50
|
-
!resource.locked
|
71
|
+
!resource.locked? &&
|
72
|
+
resource.published_at.present?
|
51
73
|
end
|
52
74
|
end
|
53
75
|
```
|
@@ -64,6 +86,76 @@ PostPolicy.authorize(:update?, User.new, Post.new(false)).granted? #=> true
|
|
64
86
|
PostPolicy.authorize(:update?, User.new, Post.new(true)).granted? #=> false
|
65
87
|
```
|
66
88
|
|
89
|
+
#### Authorization Result
|
90
|
+
|
91
|
+
Returning a String from your action is interpreted as a failure. The String is added to the messages of the authorization.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
Post = Struct.new(:author_id)
|
95
|
+
User = Struct.new(:id)
|
96
|
+
|
97
|
+
class PostPolicy
|
98
|
+
include Critic::Policy
|
99
|
+
|
100
|
+
def destroy?
|
101
|
+
return true if resource.author_id == subject.id
|
102
|
+
"Cannot destroy Post: This post is authored by #{resource.author_id}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
authorization = PostPolicy.authorize(destroy?, User.new(1), Post.new(2))
|
107
|
+
authorization.granted? #=> false
|
108
|
+
authorization.messages #=> ["Cannot destroy Post: This post is authored by 2"']
|
109
|
+
```
|
110
|
+
|
111
|
+
`halt` can be used to indicate early failure. The argument provided to `halt` becomes the result of the authorization.
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
Post = Struct.new(:author_id)
|
115
|
+
User = Struct.new(:id)
|
116
|
+
|
117
|
+
class PostPolicy
|
118
|
+
include Critic::Policy
|
119
|
+
|
120
|
+
def destroy?
|
121
|
+
if resource.author_id != subject.id
|
122
|
+
halt "Cannot destroy Post: This post is authored by #{resource.author_id}"
|
123
|
+
end
|
124
|
+
true
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
authorization = PostPolicy.authorize(destroy?, User.new(1), Post.new(2))
|
129
|
+
authorization.granted? #=> false
|
130
|
+
authorization.messages #=> ["Cannot destroy Post: This post is authored by 2"']
|
131
|
+
```
|
132
|
+
|
133
|
+
`halt(true)` indicates immediate success.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
Post = Struct.new(:author_id)
|
137
|
+
User = Struct.new(:id)
|
138
|
+
|
139
|
+
class PostPolicy
|
140
|
+
include Critic::Policy
|
141
|
+
|
142
|
+
def destroy?
|
143
|
+
check_ownership
|
144
|
+
false
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def check_ownership
|
150
|
+
halt(true) if resource.author_id == subject.id
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
authorization = PostPolicy.authorize(destroy?, User.new(1), Post.new(2))
|
155
|
+
authorization.granted? #=> false
|
156
|
+
authorization.messages #=> ["Cannot destroy Post: This post is authored by 2"']
|
157
|
+
```
|
158
|
+
|
67
159
|
#### Scopes
|
68
160
|
|
69
161
|
Scopes treat `resource` as a starting point and return a restricted set of associated resources. Policies can have any number of scopes. The default scope is `#index`.
|
@@ -79,6 +171,17 @@ class PostPolicy
|
|
79
171
|
end
|
80
172
|
```
|
81
173
|
|
174
|
+
Verify authorization using `#authorize`.
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
Post = Class.new(ActiveRecord::Base)
|
178
|
+
User = Struct.new
|
179
|
+
|
180
|
+
authorization = PostPolicy.authorize(index, User.new, Post.new(false))
|
181
|
+
authorization.granted? #=> true
|
182
|
+
authorization.result #=> <#ActiveRecord::Relation..>
|
183
|
+
```
|
184
|
+
|
82
185
|
#### Convention
|
83
186
|
|
84
187
|
It can be a useful convention to add a `?` suffix to your action methods. This allows a clear separation between actions and scopes. All other methods should be `protected`, similar to Rails controller.
|
@@ -90,12 +193,12 @@ class PostPolicy
|
|
90
193
|
|
91
194
|
# default scope
|
92
195
|
def index
|
93
|
-
|
196
|
+
resource.where(published: true)
|
94
197
|
end
|
95
198
|
|
96
199
|
# custom scope
|
97
200
|
def author_index
|
98
|
-
|
201
|
+
resource.where(author_id: subject.id)
|
99
202
|
end
|
100
203
|
|
101
204
|
# action
|
@@ -117,6 +220,8 @@ end
|
|
117
220
|
|
118
221
|
Controllers are the primary consumer of policies. Controllers ask the policy if an authenticated subject is authorized to perform a specific action on a specific resource.
|
119
222
|
|
223
|
+
#### Actions
|
224
|
+
|
120
225
|
In Rails, the policy action is inferred from `params[:action]` which corresponds to the controller action method name.
|
121
226
|
|
122
227
|
When `authorize` fails, a `Critic::AuthorizationDenied` exception is raised with reference to the performed authorization.
|
@@ -150,7 +255,7 @@ class PostController < Sinatra::Base
|
|
150
255
|
error Critic::AuthorizationDenied do |exception|
|
151
256
|
messages = exception.authorization.messages || exception.message
|
152
257
|
|
153
|
-
body {errors: [messages]}
|
258
|
+
body {errors: [*messages]}
|
154
259
|
halt 403
|
155
260
|
end
|
156
261
|
|
@@ -161,8 +266,103 @@ class PostController < Sinatra::Base
|
|
161
266
|
post.to_json
|
162
267
|
end
|
163
268
|
end
|
269
|
+
```
|
270
|
+
|
271
|
+
##### Gentle
|
164
272
|
|
273
|
+
Calling `authorized?` returns `true` or `false` instead of raising an exception.
|
165
274
|
|
275
|
+
```ruby
|
276
|
+
# app/controllers/post_controller.rb
|
277
|
+
class PostController < Sinatra::Base
|
278
|
+
include Critic::Controller
|
279
|
+
|
280
|
+
put '/posts/:id' do |id|
|
281
|
+
post = Post.find(id)
|
282
|
+
|
283
|
+
halt(403) unless authorized?(post, :update)
|
284
|
+
|
285
|
+
post.to_json
|
286
|
+
end
|
287
|
+
end
|
288
|
+
```
|
289
|
+
|
290
|
+
##### Verify authorization
|
291
|
+
|
292
|
+
`verify_authorized` enforces that the request was authorized before the response is returned. A `Critic::AuthorizationMissing` error is raised in this case. A request is authorized if `authorized?`, `authorize` or `authorizing!` is called before the response is returned.
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
# app/controllers/post_controller.rb
|
296
|
+
class PostController < Sinatra::Base
|
297
|
+
include Critic::Controller
|
298
|
+
|
299
|
+
verify_authorized
|
300
|
+
|
301
|
+
error Critic::AuthorizationMissing do |exception|
|
302
|
+
# notify developers that something has gone horribly wrong
|
303
|
+
halt 503
|
304
|
+
end
|
305
|
+
|
306
|
+
put '/posts/:id' do |id|
|
307
|
+
post = Post.find(id)
|
308
|
+
|
309
|
+
post.to_json
|
310
|
+
end
|
311
|
+
end
|
312
|
+
```
|
313
|
+
|
314
|
+
This check can be artificially skipped calling `authorizing!`.
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
# app/controllers/invitation_controller.rb
|
318
|
+
class InvitationController < Sinatra::Base
|
319
|
+
include Critic::Controller
|
320
|
+
|
321
|
+
verify_authorized
|
322
|
+
|
323
|
+
post '/invitation/accept/code' do |code|
|
324
|
+
invitation = Invitiation.find_by(code: code)
|
325
|
+
|
326
|
+
invitation.accept!
|
327
|
+
authorizing! # Skip authorization check
|
328
|
+
|
329
|
+
redirect '/'
|
330
|
+
end
|
331
|
+
end
|
332
|
+
```
|
333
|
+
|
334
|
+
#### Scopes
|
335
|
+
|
336
|
+
Use `authorize_scope` and provide the base scope. The return value is the result.
|
337
|
+
|
338
|
+
```ruby
|
339
|
+
# app/controllers/post_controller.rb
|
340
|
+
class PostController < Sinatra::Base
|
341
|
+
include Critic::Controller
|
342
|
+
|
343
|
+
get '/customers/:customer_id/posts' do |customer_id|
|
344
|
+
posts =
|
345
|
+
authorize_scope(Post.where(customer_id: customer_id))
|
346
|
+
|
347
|
+
posts.to_json
|
348
|
+
end
|
349
|
+
end
|
350
|
+
```
|
351
|
+
|
352
|
+
Custom indexes can be used by passing an `action` parameter.
|
353
|
+
|
354
|
+
```ruby
|
355
|
+
# app/controllers/post_controller.rb
|
356
|
+
class PostController < Sinatra::Base
|
357
|
+
include Critic::Controller
|
358
|
+
|
359
|
+
get '/posts' d
|
360
|
+
posts =
|
361
|
+
authorize_scope(Post, action: :custom_index)
|
362
|
+
|
363
|
+
posts.to_json
|
364
|
+
end
|
365
|
+
end
|
166
366
|
```
|
167
367
|
|
168
368
|
#### Custom subject
|
@@ -0,0 +1,78 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
critic (0.2.2)
|
5
|
+
activesupport (> 3.0, < 6.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activesupport (5.2.4.3)
|
11
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
12
|
+
i18n (>= 0.7, < 2)
|
13
|
+
minitest (~> 5.1)
|
14
|
+
tzinfo (~> 1.1)
|
15
|
+
appraisal (2.2.0)
|
16
|
+
bundler
|
17
|
+
rake
|
18
|
+
thor (>= 0.14.0)
|
19
|
+
ast (2.4.0)
|
20
|
+
coderay (1.1.2)
|
21
|
+
concurrent-ruby (1.1.6)
|
22
|
+
diff-lcs (1.3)
|
23
|
+
i18n (1.8.2)
|
24
|
+
concurrent-ruby (~> 1.0)
|
25
|
+
method_source (0.9.2)
|
26
|
+
minitest (5.14.1)
|
27
|
+
parser (2.7.1.2)
|
28
|
+
ast (~> 2.4.0)
|
29
|
+
powerpack (0.1.2)
|
30
|
+
pry (0.12.2)
|
31
|
+
coderay (~> 1.1.0)
|
32
|
+
method_source (~> 0.9.0)
|
33
|
+
pry-nav (0.3.0)
|
34
|
+
pry (>= 0.9.10, < 0.13.0)
|
35
|
+
rainbow (2.2.2)
|
36
|
+
rake
|
37
|
+
rake (10.5.0)
|
38
|
+
rspec (3.9.0)
|
39
|
+
rspec-core (~> 3.9.0)
|
40
|
+
rspec-expectations (~> 3.9.0)
|
41
|
+
rspec-mocks (~> 3.9.0)
|
42
|
+
rspec-core (3.9.2)
|
43
|
+
rspec-support (~> 3.9.3)
|
44
|
+
rspec-expectations (3.9.2)
|
45
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
46
|
+
rspec-support (~> 3.9.0)
|
47
|
+
rspec-mocks (3.9.1)
|
48
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
49
|
+
rspec-support (~> 3.9.0)
|
50
|
+
rspec-support (3.9.3)
|
51
|
+
rubocop (0.46.0)
|
52
|
+
parser (>= 2.3.1.1, < 3.0)
|
53
|
+
powerpack (~> 0.1)
|
54
|
+
rainbow (>= 1.99.1, < 3.0)
|
55
|
+
ruby-progressbar (~> 1.7)
|
56
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
57
|
+
ruby-progressbar (1.10.1)
|
58
|
+
thor (1.0.1)
|
59
|
+
thread_safe (0.3.6)
|
60
|
+
tzinfo (1.2.7)
|
61
|
+
thread_safe (~> 0.1)
|
62
|
+
unicode-display_width (1.7.0)
|
63
|
+
|
64
|
+
PLATFORMS
|
65
|
+
ruby
|
66
|
+
|
67
|
+
DEPENDENCIES
|
68
|
+
activesupport (~> 5.2)
|
69
|
+
appraisal (~> 2.1)
|
70
|
+
bundler (~> 1.10)
|
71
|
+
critic!
|
72
|
+
pry-nav
|
73
|
+
rake (~> 10.0)
|
74
|
+
rspec (~> 3.4)
|
75
|
+
rubocop (~> 0.46.0)
|
76
|
+
|
77
|
+
BUNDLED WITH
|
78
|
+
1.17.3
|
data/lib/critic/callbacks.rb
CHANGED
@@ -66,8 +66,12 @@ module Critic::Callbacks
|
|
66
66
|
from = options[from]
|
67
67
|
return unless from
|
68
68
|
|
69
|
-
|
70
|
-
options[to] =
|
69
|
+
actions = Array(options[to]) + Array(from)
|
70
|
+
options[to] = lambda {
|
71
|
+
actions.any? { |action|
|
72
|
+
authorization.action.to_s == action.to_s
|
73
|
+
}
|
74
|
+
}
|
71
75
|
end
|
72
76
|
|
73
77
|
# Skip before, after, and around action callbacks matching any of the names.
|
data/lib/critic/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: critic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josh Lane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -100,6 +100,8 @@ files:
|
|
100
100
|
- gemfiles/activesupport_4.gemfile.lock
|
101
101
|
- gemfiles/activesupport_5.gemfile
|
102
102
|
- gemfiles/activesupport_5.gemfile.lock
|
103
|
+
- gemfiles/activesupport_5_2.gemfile
|
104
|
+
- gemfiles/activesupport_5_2.gemfile.lock
|
103
105
|
- lib/critic.rb
|
104
106
|
- lib/critic/authorization.rb
|
105
107
|
- lib/critic/authorization_denied.rb
|
@@ -127,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
129
|
version: '0'
|
128
130
|
requirements: []
|
129
131
|
rubyforge_project:
|
130
|
-
rubygems_version: 2.5.2
|
132
|
+
rubygems_version: 2.5.2.3
|
131
133
|
signing_key:
|
132
134
|
specification_version: 4
|
133
135
|
summary: Resource authorization
|