action_authorization 0.3.4 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 738c9014d3f4738e858d3fed5b026ddf9481f856b814421fe4a2bc7ddcd7c478
4
- data.tar.gz: e3fcdc5d42fd8071f0b1c28260197f5dc5f7c748b3c4cd02f5968328e58818c4
3
+ metadata.gz: d6ff507f71e00e904d3e19c0b9d042e9d62155c1496c6cebb1fc2280d602ee1d
4
+ data.tar.gz: e771b6625111854c345974406b7dceb8afd3c436e5106e4e7a4381d9fde5cd5f
5
5
  SHA512:
6
- metadata.gz: a658d363a670eb08452b23352519747317a8415fd49b6a90bd42c534619c441aa98f572a263f3fb731b67b627c32c62a9da190853216287873ab42bd90ee13a4
7
- data.tar.gz: '0993d92a305756013707f6391e0368998f742675db8e96765905456b713a35deae8bfcf903df131f6a27d9245c7c091155620b53327236f7cc63da828cca1882'
6
+ metadata.gz: 94a2acc764e1e6b3fce5652e82d6d2221810423ac0a4e6191adec662c249219ccec77c8b33abd1ffb6f5cf52a1cdf73d2e38e0620b422ecdedb501af8d4c485f
7
+ data.tar.gz: 4933805e2ffd1ebc2ecc7c3e15657106831eadf0ed3be7200af729b11f2c6d09af555113365b0156a7ae0239097d60bc4339885140dcd703510359b4e6436d39
data/README.md CHANGED
@@ -1,7 +1,9 @@
1
1
  # ActionAuthorization
2
+
2
3
  A base policy class for authorizing controller actions with access to the current_user and object.
3
4
 
4
5
  ## Installation
6
+
5
7
  Add this line to your application's Gemfile:
6
8
 
7
9
  ```ruby
@@ -9,19 +11,23 @@ gem 'action_authorization'
9
11
  ```
10
12
 
11
13
  And then execute:
14
+
12
15
  ```bash
13
16
  bundle
14
17
  ```
15
18
 
16
19
  Or install it with:
20
+
17
21
  ```bash
18
22
  gem install action_authorization
19
23
  ```
20
24
 
21
25
  ## Requirements
26
+
22
27
  ActionAuthorization requires a **current_user** method that returns the currently logged in user.
23
28
 
24
29
  ## Usage
30
+
25
31
  Include the ActionAuthorization module in your ApplicationController (or indvidual controller(s))
26
32
 
27
33
  ```ruby
@@ -31,7 +37,8 @@ end
31
37
  ```
32
38
 
33
39
  Create an authorization policy for a resource.
34
- ``` ruby
40
+
41
+ ```ruby
35
42
  class DocumentPolicy < ActionAuthorization::BasePolicy
36
43
  def show?
37
44
  document.owner == user
@@ -40,6 +47,7 @@ end
40
47
  ```
41
48
 
42
49
  Call **authorize** method in controller action.
50
+
43
51
  ```ruby
44
52
  class DocumentController < ApplicationController
45
53
  def show
@@ -49,6 +57,7 @@ end
49
57
  ```
50
58
 
51
59
  Pass a **policy_class** to authorize to override the default resource based policy.
60
+
52
61
  ```ruby
53
62
  class DocumentController < ApplicationController
54
63
  def show
@@ -57,11 +66,142 @@ class DocumentController < ApplicationController
57
66
  end
58
67
  ```
59
68
 
69
+ By default, an unauthorized action raises `ActionAuthorization::AuthorizationFailure`.
70
+ Applications can handle it in their base controller and choose an appropriate response:
71
+
72
+ ```ruby
73
+ class ApplicationController < ActionController::Base
74
+ rescue_from ActionAuthorization::AuthorizationFailure, with: :authorization_failure
75
+
76
+ private
77
+
78
+ def authorization_failure
79
+ head :forbidden
80
+ end
81
+ end
82
+ ```
83
+
84
+ ### `policy`
85
+
60
86
  Check if authorized before displaying a link in the view.
61
87
 
62
88
  ```erb
63
89
  <%= link_to(@document.name, @document) if policy(@document).show? %>
64
90
  ```
65
91
 
92
+ ### `policy_for`
93
+
94
+ An enhanced version of `policy` that also accepts a `[parent, model]` pair for authorizing a new
95
+ nested record.
96
+
97
+ ```ruby
98
+ policy_for([folder, Document])
99
+ ```
100
+
101
+ This creates the model with the parent assigned to its association and returns its policy. Unlike
102
+ `folder.documents.build`, it does not add the new record to the parent's cached association.
103
+
104
+ When passed a single object, `policy_for` behaves the same as `policy`:
105
+
106
+ ```ruby
107
+ policy_for(document)
108
+ ```
109
+
110
+ ## Testing support
111
+
112
+ Testing helpers are opt-in and are loaded separately from the runtime API:
113
+
114
+ ```ruby
115
+ require "action_authorization/testing"
116
+ ```
117
+
118
+ The shared `PolicyHelpers` module provides `allow` and `forbid`. It requires including modules to
119
+ define `policy_class_default`, so include the specialized module for the test type rather than
120
+ including `PolicyHelpers` directly:
121
+
122
+ ```ruby
123
+ class ActionDispatch::IntegrationTest
124
+ include ActionAuthorization::Testing::IntegrationTestPolicyHelpers
125
+ end
126
+
127
+ class ActionView::TestCase
128
+ include ActionAuthorization::Testing::ViewTestPolicyHelpers
129
+ end
130
+ ```
131
+
132
+ `IntegrationTestPolicyHelpers` derives the policy class from the test class name (for example,
133
+ `DocumentsControllerTest` uses `DocumentPolicy`). `ViewTestPolicyHelpers` defaults to `OpenPolicy`
134
+ and includes `PolicyStub`, so `policy` returns an open policy in view tests. Both modules provide
135
+ `allow` and `forbid`.
136
+
137
+ If needed, `PolicyStub` can also be included separately in another test class to make `policy`
138
+ return an open policy:
139
+
140
+ ```ruby
141
+ class SomeHelperTest < ActiveSupport::TestCase
142
+ include ActionAuthorization::Testing::PolicyStub
143
+ end
144
+ ```
145
+
146
+ `OpenPolicy` allows every action by default, including non-standard predicates such as
147
+ `activate?`. Use `forbid(:activate)` to override a specific action.
148
+
149
+ The expectation helpers use the `any_instance.expects` API and require Mocha to be configured by
150
+ the client application's test suite.
151
+
152
+ ### Examples
153
+
154
+ In an integration test, `allow` and `forbid` set the result of the action predicate on the policy
155
+ class inferred from the test class name. For `DocumentsControllerTest`, that is `DocumentPolicy`:
156
+
157
+ ```ruby
158
+ class DocumentsControllerTest < ActionDispatch::IntegrationTest
159
+ test "shows an authorized document" do
160
+ allow(:show)
161
+
162
+ get document_url(documents(:one))
163
+ assert_response :success
164
+ end
165
+
166
+ test "raises when showing a forbidden document" do
167
+ forbid(:show)
168
+
169
+ assert_raises(ActionAuthorization::AuthorizationFailure) do
170
+ get document_url(documents(:one))
171
+ end
172
+ end
173
+ end
174
+ ```
175
+
176
+ In a view test, `ViewTestPolicyHelpers` uses `OpenPolicy`, so the link is allowed by default. Use
177
+ `forbid(:show)` to exercise the hidden-link case:
178
+
179
+ ```ruby
180
+ class DocumentsHelperTest < ActionView::TestCase
181
+ test "returns a link to an authorized document" do
182
+ assert link_to_document(documents(:one))
183
+ end
184
+
185
+ test "returns nil when showing the document is forbidden" do
186
+ forbid(:show)
187
+
188
+ assert_nil link_to_document(documents(:one))
189
+ end
190
+ end
191
+ ```
192
+
193
+ Helpers that render a template or partial that calls `policy` need the view object stubbed too.
194
+ Call `view_policy_stub` in those tests:
195
+
196
+ ```ruby
197
+ test "returns the document header" do
198
+ view_policy_stub
199
+
200
+ assert_includes document_header(documents(:one)), "Document"
201
+ end
202
+ ```
203
+
66
204
  ## License
67
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
205
+
206
+ The gem is available as open source under the terms of the
207
+ [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionAuthorization
4
+ module Testing
5
+ module IntegrationTestPolicyHelpers
6
+ include PolicyHelpers
7
+
8
+ private
9
+
10
+ def policy_class_default
11
+ self.class.name.sub("ControllerTest", "").singularize.concat("Policy").constantize
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionAuthorization
4
+ module Testing
5
+ class OpenPolicy < ActionAuthorization::BasePolicy
6
+ def initialize
7
+ super(nil, nil)
8
+ end
9
+
10
+ # Any predicate (ending in "?") is allowed, including non-standard actions.
11
+ def method_missing(method_name, *, &)
12
+ predicate?(method_name) || super
13
+ end
14
+
15
+ def respond_to_missing?(method_name, include_private = false)
16
+ predicate?(method_name) || super
17
+ end
18
+
19
+ private
20
+
21
+ def authorized?
22
+ true
23
+ end
24
+
25
+ def predicate?(method_name)
26
+ method_name.end_with?("?")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionAuthorization
4
+ module Testing
5
+ module PolicyHelpers
6
+ private
7
+
8
+ def allow(action, **)
9
+ policy_action_expectation(action, **)
10
+ end
11
+
12
+ def forbid(action, **)
13
+ policy_action_expectation(action, **, value: false)
14
+ end
15
+
16
+ def policy_action_expectation(action, policy_class: policy_class_default, value: true)
17
+ policy_class.any_instance.expects("#{action}?").returns(value)
18
+ end
19
+
20
+ def policy_class_default
21
+ raise NotImplementedError, "You must implement policy_class_default in your test class"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionAuthorization
4
+ module Testing
5
+ module PolicyStub
6
+ def policy(*)
7
+ OpenPolicy.new
8
+ end
9
+ alias policy_for policy
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionAuthorization
4
+ module Testing
5
+ module ViewTestPolicyHelpers
6
+ include PolicyHelpers
7
+ include PolicyStub
8
+
9
+ private
10
+
11
+ def policy_class_default
12
+ OpenPolicy
13
+ end
14
+
15
+ def view_policy_stub
16
+ view.stubs(:policy).returns(policy)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_authorization"
4
+ require "action_authorization/testing/open_policy"
5
+ require "action_authorization/testing/policy_stub"
6
+ require "action_authorization/testing/policy_helpers"
7
+ require "action_authorization/testing/integration_test_policy_helpers"
8
+ require "action_authorization/testing/view_test_policy_helpers"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionAuthorization
4
- VERSION = "0.3.4"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -10,7 +10,7 @@ require "action_authorization/authorization_failure"
10
10
  module ActionAuthorization
11
11
  def self.included(base)
12
12
  base.class_eval do
13
- helper_method :policy
13
+ helper_method :policy, :policy_for
14
14
  end
15
15
  end
16
16
 
@@ -26,7 +26,18 @@ module ActionAuthorization
26
26
  (policy_class || policy_class_for(object)).new(current_user, object)
27
27
  end
28
28
 
29
+ def policy_for(object)
30
+ policy(object.is_a?(Array) ? policy_record_for(object) : object)
31
+ end
32
+
29
33
  def policy_class_for(object)
30
34
  object.nil? ? NullPolicy : "#{object.model_name}Policy".constantize
31
35
  end
36
+
37
+ private
38
+
39
+ def policy_record_for(array)
40
+ parent, model = array
41
+ model.new(parent.model_name.element.to_sym => parent)
42
+ end
32
43
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_authorization
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Baldwin
@@ -39,6 +39,12 @@ files:
39
39
  - lib/action_authorization/authorization_failure.rb
40
40
  - lib/action_authorization/base_policy.rb
41
41
  - lib/action_authorization/null_policy.rb
42
+ - lib/action_authorization/testing.rb
43
+ - lib/action_authorization/testing/integration_test_policy_helpers.rb
44
+ - lib/action_authorization/testing/open_policy.rb
45
+ - lib/action_authorization/testing/policy_helpers.rb
46
+ - lib/action_authorization/testing/policy_stub.rb
47
+ - lib/action_authorization/testing/view_test_policy_helpers.rb
42
48
  - lib/action_authorization/version.rb
43
49
  homepage: https://github.com/wwidea/action_authorization
44
50
  licenses:
@@ -60,7 +66,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
60
66
  - !ruby/object:Gem::Version
61
67
  version: '0'
62
68
  requirements: []
63
- rubygems_version: 4.0.2
69
+ rubygems_version: 4.0.17
64
70
  specification_version: 4
65
71
  summary: Rails controller object-level action authorization.
66
72
  test_files: []