critic 0.2.2 → 0.2.3

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: 4094e59ee71c2769e9e05d891dc36e4a95dd8aa2
4
- data.tar.gz: 24336a0cf53571ea45cccd651f49fda0758d2e68
3
+ metadata.gz: 1f826726f9d8ac8cc997b05fdbc56a8ccc1b5a60
4
+ data.tar.gz: 18a60cef6bfc4fbb47b9e0abd600663caae9b8cc
5
5
  SHA512:
6
- metadata.gz: ff851589a932c9e65322d6df6625d35a3148226d330842765262d70770b43c93493932ab70471094a819149be7017c42aaa61c74fd39e39006ecdfce11768a39
7
- data.tar.gz: 201e017b6fe055d0779ab2cf09714082660266264cdb95614cc58fc1668199df9ed3ebefec0ade24197bedc2e66dee6aa12ddd8a08ef6b3f9e9aa344dc36ca81
6
+ metadata.gz: 4990660d3ea2c6e66d97c27fe5cd5f2014da4ef206783cc4b0203eb07454c9b94d0c4838797465ea9dab0b6a9f4b31469853b9752ba5efa348e41791edc32758
7
+ data.tar.gz: 9167566dd54f8224645edcc09925466f39b35d5c5d9e3cb0a6a5771d2813a931af24067fd4c448a9dfe9b237eaf442169d4cb87e39ba55ded176c3579c66623b
@@ -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
@@ -1,6 +1,23 @@
1
- # Change Log
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 Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
42
+ \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
@@ -1,7 +1,21 @@
1
- Copyright (c) 2016 Joshua Lane
1
+ MIT License
2
2
 
3
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
3
+ Copyright (c) 2017 Josh Lane
4
4
 
5
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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
- Post.where(published: true)
196
+ resource.where(published: true)
94
197
  end
95
198
 
96
199
  # custom scope
97
200
  def author_index
98
- Post.where(author_id: subject.id)
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,13 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activesupport", "~> 5.2"
6
+ gem "appraisal", "~> 2.1"
7
+ gem "rubocop", "~> 0.46.0", :require => false
8
+
9
+ group :test do
10
+ gem "pry-nav"
11
+ end
12
+
13
+ gemspec :path => "../"
@@ -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
@@ -66,8 +66,12 @@ module Critic::Callbacks
66
66
  from = options[from]
67
67
  return unless from
68
68
 
69
- from = Array(from).map { |o| "authorization.action.to_s == '#{o}'" }
70
- options[to] = Array(options[to]).unshift(from).join(' || ')
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.
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Critic
3
- VERSION = '0.2.2'
3
+ VERSION = '0.2.3'
4
4
  end
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.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: 2017-01-04 00:00:00.000000000 Z
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