rspec-authorization 0.0.2 → 0.0.6
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 +3 -0
- data/Appraisals +7 -0
- data/Gemfile +9 -5
- data/HISTORY.md +20 -1
- data/README.md +28 -3
- data/gemfiles/rails_4.1.9.gemfile +28 -0
- data/gemfiles/rails_4.1.9.gemfile.lock +234 -0
- data/gemfiles/rails_4.2.0.gemfile +28 -0
- data/gemfiles/rails_4.2.0.gemfile.lock +260 -0
- data/lib/rspec/authorization/adapters.rb +3 -0
- data/lib/rspec/authorization/adapters/privilege.rb +14 -0
- data/lib/rspec/authorization/adapters/request.rb +4 -1
- data/lib/rspec/authorization/adapters/resource.rb +58 -0
- data/lib/rspec/authorization/adapters/restful_helper_method.rb +137 -0
- data/lib/rspec/authorization/matchers/have_permission_for.rb +93 -70
- data/lib/rspec/authorization/version.rb +1 -1
- data/rspec-authorization.gemspec +2 -1
- data/spec/controllers/articles_controller_spec.rb +3 -11
- data/spec/lib/rspec/authorization/adapters/resource_spec.rb +102 -0
- data/spec/lib/rspec/authorization/adapters/restful_helper_method_spec.rb +151 -0
- data/spec/lib/rspec/authorization/matchers/have_permission_for_spec.rb +81 -0
- data/spec/rails_helper.rb +1 -0
- data/tools/rails_test_app/template.rb +9 -5
- metadata +36 -2
@@ -21,7 +21,7 @@ module RSpec::Authorization
|
|
21
21
|
# it { is_expected.to have_permission_for(:user).to(:index) }
|
22
22
|
# end
|
23
23
|
#
|
24
|
-
# Currently this matcher only support
|
24
|
+
# Currently this matcher only support restful action check, to check the
|
25
25
|
# controller against +config/authorization_rules.rb+. Skipping the +#to+
|
26
26
|
# method will result in default action assigned as +:index+.
|
27
27
|
#
|
@@ -29,36 +29,20 @@ module RSpec::Authorization
|
|
29
29
|
#
|
30
30
|
# it { is_expected.to have_permission_for(:user) }
|
31
31
|
#
|
32
|
-
# === RESTful methods
|
32
|
+
# === RESTful helper methods
|
33
33
|
#
|
34
|
-
# For your convenience, there are
|
35
|
-
# from the matcher,
|
36
|
-
#
|
37
|
-
# - +to_read+
|
38
|
-
# - +to_create+
|
39
|
-
# - +to_update+
|
40
|
-
# - +to_delete+
|
41
|
-
#
|
42
|
-
# Consider the following example:
|
34
|
+
# For your convenience, there are restful helper methods available to be
|
35
|
+
# chained from the matcher, consider the following example:
|
43
36
|
#
|
44
37
|
# it { is_expected.to have_permission_for(:user).to_read }
|
45
|
-
# it { is_expected.to have_permission_for(:user).
|
38
|
+
# it { is_expected.to have_permission_for(:user).to_create }
|
46
39
|
# it { is_expected.not_to have_permission_for(:user).to_update }
|
47
40
|
# it { is_expected.not_to have_permission_for(:user).to_delete }
|
41
|
+
# it { is_expected.not_to have_permission_for(:user).to_manage }
|
48
42
|
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
# Method RESTful action
|
54
|
-
# ------------------------------------
|
55
|
-
# to_read [:index, :show]
|
56
|
-
# to_edit [:edit, :update]
|
57
|
-
# to_create [:new, :create]
|
58
|
-
# to_delete [:destroy]
|
59
|
-
#
|
60
|
-
# Matcher for RESTful methods is slightly different than that of a single
|
61
|
-
# method, following is how RESTful methods request results evaluated:
|
43
|
+
# Matcher for restful helper methods is slightly different than that of
|
44
|
+
# a single method, following is an example of how restful helper methods
|
45
|
+
# request results evaluated:
|
62
46
|
#
|
63
47
|
# all_requests (of #to_read) matches? does_not_match?
|
64
48
|
# -------------------------------------------------------------------
|
@@ -66,7 +50,48 @@ module RSpec::Authorization
|
|
66
50
|
# {index: true, show: false} false false
|
67
51
|
# {index: false, show: false} false true
|
68
52
|
#
|
53
|
+
# === Focused RESTful helper methods
|
54
|
+
#
|
55
|
+
# There are cases where you need to focused your matching only for a given
|
56
|
+
# criteria, let's say only match for read actions, or match except delete
|
57
|
+
# action, consider the following example:
|
58
|
+
#
|
59
|
+
# it { is_expected.to have_permission_for(:user).only_to_read }
|
60
|
+
# it { is_expected.to have_permission_for(:writer).except_to_delete }
|
61
|
+
#
|
62
|
+
# The above statements have their negated counterparts, consider the
|
63
|
+
# following example:
|
64
|
+
#
|
65
|
+
# it { is_expected.not_to have_permission_for(:user).only_to_read }
|
66
|
+
# it { is_expected.not_to have_permission_for(:writer).only_to_delete }
|
67
|
+
#
|
68
|
+
# If you see the above negated matcher, they actually have a relationship
|
69
|
+
# to the other's negated counterpart instead of theirs, consider the following
|
70
|
+
# example:
|
71
|
+
#
|
72
|
+
# it { is_expected.to have_permission_for(:user).only_to_read }
|
73
|
+
# it { is_expected.not_to have_permission_for(:user).except_to_read }
|
74
|
+
#
|
75
|
+
# The above examples are doing exactly the same thing, so does the following
|
76
|
+
# example, these examples below also doing exactly the same thing and can
|
77
|
+
# be used in either case:
|
78
|
+
#
|
79
|
+
# it { is_expected.to have_permission_for(:writer).except_to_delete }
|
80
|
+
# it { is_expected.not_to have_permission_for(:writer).only_to_read }
|
81
|
+
#
|
82
|
+
# That means, the following example, is actually negating each other and
|
83
|
+
# can be used to negate your statements instead of using the negated version
|
84
|
+
# of the matcher:
|
85
|
+
#
|
86
|
+
# it { is_expected.to have_permission_for(:user).only_to_read }
|
87
|
+
# it { is_expected.to have_permission_for(:user).except_to_read }
|
88
|
+
#
|
89
|
+
# Even if you can have a negated matcher using a focused restful helper
|
90
|
+
# methods, it is better to stick with the possitive matcher, negated matcher
|
91
|
+
# can easily confuse you, and it only serves the purpose of completeness.
|
92
|
+
#
|
69
93
|
# @param role [Symbol] role name to matched against
|
94
|
+
# @see RestfulHelperMethod
|
70
95
|
def have_permission_for(role)
|
71
96
|
HavePermissionFor.new(role)
|
72
97
|
end
|
@@ -74,85 +99,83 @@ module RSpec::Authorization
|
|
74
99
|
class HavePermissionFor # :nodoc: all
|
75
100
|
include Adapters
|
76
101
|
|
77
|
-
attr_reader :
|
102
|
+
attr_reader :role, :prefix, :action
|
103
|
+
attr_reader :resource, :restful_helper_method, :privilege
|
104
|
+
attr_reader :actions, :negated_actions
|
78
105
|
|
79
106
|
def initialize(role)
|
80
107
|
@role = role
|
108
|
+
|
109
|
+
@actions = [:index]
|
110
|
+
@negated_actions = []
|
81
111
|
end
|
82
112
|
|
83
113
|
def to(action)
|
84
|
-
@
|
85
|
-
@
|
114
|
+
@prefix = :to
|
115
|
+
@action = action
|
116
|
+
@actions = [action]
|
86
117
|
|
87
118
|
self
|
88
119
|
end
|
89
120
|
|
90
|
-
def
|
91
|
-
@
|
92
|
-
@actions = %i(index show)
|
93
|
-
|
94
|
-
self
|
95
|
-
end
|
121
|
+
def method_missing(method_name, *args, &block)
|
122
|
+
@restful_helper_method = RestfulHelperMethod.new(method_name)
|
96
123
|
|
97
|
-
|
98
|
-
@
|
99
|
-
@actions = %i(new create)
|
124
|
+
@actions = restful_helper_method.actions
|
125
|
+
@negated_actions = restful_helper_method.negated_actions
|
100
126
|
|
101
127
|
self
|
102
128
|
end
|
103
129
|
|
104
|
-
def
|
105
|
-
|
106
|
-
@actions = %i(edit update)
|
130
|
+
def matches?(controller)
|
131
|
+
build_resource(controller)
|
107
132
|
|
108
|
-
|
133
|
+
resource.run_all
|
134
|
+
resource.permitted?
|
109
135
|
end
|
110
136
|
|
111
|
-
def
|
112
|
-
|
113
|
-
@actions = %i(destroy)
|
137
|
+
def does_not_match?(controller)
|
138
|
+
build_resource(controller)
|
114
139
|
|
115
|
-
|
140
|
+
resource.run_all
|
141
|
+
resource.forbidden?
|
116
142
|
end
|
117
143
|
|
118
|
-
def
|
119
|
-
|
120
|
-
@actions = %i(index show new create edit update destroy)
|
121
|
-
|
122
|
-
self
|
144
|
+
def failure_message
|
145
|
+
"Expected #{common_failure_message}"
|
123
146
|
end
|
124
147
|
|
125
|
-
def
|
126
|
-
|
127
|
-
request = Request.new(controller.class, action, role)
|
128
|
-
[action, request.response.status != 403]
|
129
|
-
end
|
148
|
+
def failure_message_when_negated
|
149
|
+
"Did not expect #{common_failure_message}"
|
130
150
|
end
|
131
151
|
|
132
|
-
def
|
133
|
-
|
134
|
-
@results = Hash[all_requests]
|
135
|
-
|
136
|
-
true unless results.value? false
|
152
|
+
def description
|
153
|
+
"have permission for #{role} #{humanized_behavior}"
|
137
154
|
end
|
138
155
|
|
139
|
-
|
140
|
-
@controller = controller
|
141
|
-
@results = Hash[all_requests]
|
156
|
+
private
|
142
157
|
|
143
|
-
|
158
|
+
def build_resource(controller)
|
159
|
+
@privilege = Privilege.new(
|
160
|
+
actions: actions,
|
161
|
+
negated_actions: negated_actions,
|
162
|
+
controller_class: controller.class,
|
163
|
+
role: role
|
164
|
+
)
|
165
|
+
|
166
|
+
@resource = Resource.new(privilege)
|
144
167
|
end
|
145
168
|
|
146
|
-
def
|
147
|
-
|
169
|
+
def humanized_behavior
|
170
|
+
restful_helper_method.try(:humanize) || "#{@prefix} #{action}"
|
148
171
|
end
|
149
172
|
|
150
|
-
def
|
151
|
-
"
|
173
|
+
def common_failure_message
|
174
|
+
"#{resource.controller_class} to #{description}. #{debug_results}"
|
152
175
|
end
|
153
176
|
|
154
|
-
def
|
155
|
-
"
|
177
|
+
def debug_results
|
178
|
+
"results: #{resource.results}, negated_results: #{resource.negated_results}"
|
156
179
|
end
|
157
180
|
end
|
158
181
|
end
|
data/rspec-authorization.gemspec
CHANGED
@@ -18,8 +18,9 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
20
|
spec.add_runtime_dependency "declarative_authorization"
|
21
|
-
spec.add_runtime_dependency "rspec-rails", "~> 3.0"
|
21
|
+
spec.add_runtime_dependency "rspec-rails", "~> 3.0", "< 3.2"
|
22
22
|
|
23
|
+
spec.add_development_dependency "appraisal"
|
23
24
|
spec.add_development_dependency "bundler", "~> 1.7"
|
24
25
|
spec.add_development_dependency "rails", "~> 4.0"
|
25
26
|
spec.add_development_dependency "rake", "~> 10.0"
|
@@ -7,15 +7,7 @@ describe ArticlesController do
|
|
7
7
|
it { is_expected.not_to have_permission_for(:user).to_update }
|
8
8
|
it { is_expected.not_to have_permission_for(:user).to_delete }
|
9
9
|
|
10
|
-
it { is_expected.to have_permission_for(:premium).
|
11
|
-
it { is_expected.
|
12
|
-
it { is_expected.
|
13
|
-
it { is_expected.not_to have_permission_for(:premium).to_delete }
|
14
|
-
|
15
|
-
it { is_expected.to have_permission_for(:writer).to_read }
|
16
|
-
it { is_expected.to have_permission_for(:writer).to_create }
|
17
|
-
it { is_expected.not_to have_permission_for(:writer).to_update }
|
18
|
-
it { is_expected.not_to have_permission_for(:writer).to_delete }
|
19
|
-
|
20
|
-
it { is_expected.to have_permission_for(:editor).to_manage }
|
10
|
+
it { is_expected.to have_permission_for(:premium).only_to_read }
|
11
|
+
it { is_expected.to have_permission_for(:writer ).except_to_delete }
|
12
|
+
it { is_expected.to have_permission_for(:editor ).to_manage }
|
21
13
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
include RSpec::Authorization::Adapters
|
4
|
+
|
5
|
+
describe Resource do
|
6
|
+
let(:privilege) { :privilege }
|
7
|
+
let(:resource) { Resource.new(privilege) }
|
8
|
+
|
9
|
+
subject { resource }
|
10
|
+
|
11
|
+
its(:privilege) { is_expected.to eq privilege }
|
12
|
+
|
13
|
+
specify { is_expected.to delegate_method(:actions).to(:privilege) }
|
14
|
+
specify { is_expected.to delegate_method(:negated_actions).to(:privilege) }
|
15
|
+
specify { is_expected.to delegate_method(:controller_class).to(:privilege) }
|
16
|
+
specify { is_expected.to delegate_method(:role).to(:privilege) }
|
17
|
+
|
18
|
+
describe "#run_all" do
|
19
|
+
before do
|
20
|
+
expect(resource).to receive(:requests)
|
21
|
+
expect(resource).to receive(:negated_requests)
|
22
|
+
end
|
23
|
+
|
24
|
+
specify { resource.run_all }
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#permitted?" do
|
28
|
+
context "no negated actions" do
|
29
|
+
before { allow(resource).to receive(:permitted_for?).and_return(value) }
|
30
|
+
|
31
|
+
context "permitted for actions" do
|
32
|
+
let(:value) { true }
|
33
|
+
|
34
|
+
specify { expect(resource.permitted?).to be_truthy }
|
35
|
+
end
|
36
|
+
|
37
|
+
context "not permitted for actions" do
|
38
|
+
let(:value) { false }
|
39
|
+
|
40
|
+
specify { expect(resource.permitted?).to be_falsy }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "have negated actions" do
|
45
|
+
before do
|
46
|
+
allow(resource).to receive(:negated_results).and_return([:present])
|
47
|
+
allow(resource).to receive(:permitted_for?).and_return(permitted_value)
|
48
|
+
allow(resource).to receive(:forbidden_for?).and_return(true)
|
49
|
+
end
|
50
|
+
|
51
|
+
context "permitted for actions" do
|
52
|
+
let(:permitted_value) { true }
|
53
|
+
|
54
|
+
specify { expect(resource.permitted?).to be_truthy }
|
55
|
+
end
|
56
|
+
|
57
|
+
context "not permitted for actions" do
|
58
|
+
let(:permitted_value) { false }
|
59
|
+
|
60
|
+
specify { expect(resource.permitted?).to be_falsy }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#forbidden?" do
|
66
|
+
context "no negated actions" do
|
67
|
+
before { allow(resource).to receive(:forbidden_for?).and_return(value) }
|
68
|
+
|
69
|
+
context "forbidden for actions" do
|
70
|
+
let(:value) { true }
|
71
|
+
|
72
|
+
specify { expect(resource.forbidden?).to be_truthy }
|
73
|
+
end
|
74
|
+
|
75
|
+
context "not forbidden for actions" do
|
76
|
+
let(:value) { false }
|
77
|
+
|
78
|
+
specify { expect(resource.forbidden?).to be_falsy }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "have negated actions" do
|
83
|
+
before do
|
84
|
+
allow(resource).to receive(:negated_results).and_return([:present])
|
85
|
+
allow(resource).to receive(:forbidden_for?).and_return(value)
|
86
|
+
allow(resource).to receive(:permitted_for?).and_return(true)
|
87
|
+
end
|
88
|
+
|
89
|
+
context "forbidden for actions" do
|
90
|
+
let(:value) { true }
|
91
|
+
|
92
|
+
specify { expect(resource.forbidden?).to be_truthy }
|
93
|
+
end
|
94
|
+
|
95
|
+
context "not forbidden for actions" do
|
96
|
+
let(:value) { false }
|
97
|
+
|
98
|
+
specify { expect(resource.forbidden?).to be_falsy }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
include RSpec::Authorization::Adapters
|
4
|
+
|
5
|
+
describe RestfulHelperMethod do
|
6
|
+
let(:restful_helper_method) { RestfulHelperMethod.new(name) }
|
7
|
+
|
8
|
+
subject { restful_helper_method }
|
9
|
+
|
10
|
+
describe "#humanize" do
|
11
|
+
let(:name) { :only_to_read }
|
12
|
+
|
13
|
+
specify { expect(restful_helper_method.humanize).to eq "only to read" }
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#to_a" do
|
17
|
+
let(:name) { :to_read }
|
18
|
+
let(:actions) { %i(list of action from actions) }
|
19
|
+
let(:negated_actions) { %i(list of negated action from negated_actions) }
|
20
|
+
|
21
|
+
before do
|
22
|
+
allow(restful_helper_method).to receive(:actions).and_return(actions)
|
23
|
+
allow(restful_helper_method).to receive(:negated_actions).and_return(negated_actions)
|
24
|
+
end
|
25
|
+
|
26
|
+
context "implicitly infers to array" do
|
27
|
+
specify { expect([*restful_helper_method]).to eq [actions, negated_actions] }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "method unavailable" do
|
32
|
+
specify { expect{ RestfulHelperMethod.new(:to_explode) }.to raise_error NoMethodError }
|
33
|
+
end
|
34
|
+
|
35
|
+
context "restful methods" do
|
36
|
+
let(:name) { :to_read }
|
37
|
+
its(:prefix) { is_expected.to eq :to }
|
38
|
+
its(:negated_actions) { is_expected.to eq %i() }
|
39
|
+
|
40
|
+
context "to_read" do
|
41
|
+
let(:name) { :to_read }
|
42
|
+
|
43
|
+
its(:behavior) { is_expected.to eq :read }
|
44
|
+
its(:actions) { is_expected.to eq %i(index show) }
|
45
|
+
end
|
46
|
+
|
47
|
+
context "to_create" do
|
48
|
+
let(:name) { :to_create }
|
49
|
+
|
50
|
+
its(:behavior) { is_expected.to eq :create }
|
51
|
+
its(:actions) { is_expected.to eq %i(new create) }
|
52
|
+
end
|
53
|
+
|
54
|
+
context "to_update" do
|
55
|
+
let(:name) { :to_update }
|
56
|
+
|
57
|
+
its(:behavior) { is_expected.to eq :update }
|
58
|
+
its(:actions) { is_expected.to eq %i(edit update) }
|
59
|
+
end
|
60
|
+
|
61
|
+
context "to_delete" do
|
62
|
+
let(:name) { :to_delete }
|
63
|
+
|
64
|
+
its(:behavior) { is_expected.to eq :delete }
|
65
|
+
its(:actions) { is_expected.to eq %i(destroy) }
|
66
|
+
end
|
67
|
+
|
68
|
+
context "to_manage" do
|
69
|
+
let(:name) { :to_manage }
|
70
|
+
|
71
|
+
its(:behavior) { is_expected.to eq :manage }
|
72
|
+
its(:actions) { is_expected.to eq %i(index show new create edit update destroy) }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "focused restful methods" do
|
77
|
+
context "only_to" do
|
78
|
+
let(:name) { :only_to_read }
|
79
|
+
its(:prefix) { is_expected.to eq :only_to }
|
80
|
+
|
81
|
+
context "only_to_read" do
|
82
|
+
let(:name) { :only_to_read }
|
83
|
+
|
84
|
+
its(:behavior) { is_expected.to eq :read }
|
85
|
+
its(:actions) { is_expected.to eq %i(index show) }
|
86
|
+
its(:negated_actions) { is_expected.to eq %i(new create edit update destroy) }
|
87
|
+
end
|
88
|
+
|
89
|
+
context "only_to_create" do
|
90
|
+
let(:name) { :only_to_create }
|
91
|
+
|
92
|
+
its(:behavior) { is_expected.to eq :create }
|
93
|
+
its(:actions) { is_expected.to eq %i(new create) }
|
94
|
+
its(:negated_actions) { is_expected.to eq %i(index show edit update destroy) }
|
95
|
+
end
|
96
|
+
|
97
|
+
context "only_to_update" do
|
98
|
+
let(:name) { :only_to_update }
|
99
|
+
|
100
|
+
its(:behavior) { is_expected.to eq :update }
|
101
|
+
its(:actions) { is_expected.to eq %i(edit update) }
|
102
|
+
its(:negated_actions) { is_expected.to eq %i(index show new create destroy) }
|
103
|
+
end
|
104
|
+
|
105
|
+
context "only_to_delete" do
|
106
|
+
let(:name) { :only_to_delete }
|
107
|
+
|
108
|
+
its(:behavior) { is_expected.to eq :delete }
|
109
|
+
its(:actions) { is_expected.to eq %i(destroy) }
|
110
|
+
its(:negated_actions) { is_expected.to eq %i(index show new create edit update) }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "except_to" do
|
115
|
+
let(:name) { :except_to_read }
|
116
|
+
its(:prefix) { is_expected.to eq :except_to }
|
117
|
+
|
118
|
+
context "except_to_read" do
|
119
|
+
let(:name) { :except_to_read }
|
120
|
+
|
121
|
+
its(:behavior) { is_expected.to eq :read }
|
122
|
+
its(:actions) { is_expected.to eq %i(new create edit update destroy) }
|
123
|
+
its(:negated_actions) { is_expected.to eq %i(index show) }
|
124
|
+
end
|
125
|
+
|
126
|
+
context "except_to_create" do
|
127
|
+
let(:name) { :except_to_create }
|
128
|
+
|
129
|
+
its(:behavior) { is_expected.to eq :create }
|
130
|
+
its(:actions) { is_expected.to eq %i(index show edit update destroy) }
|
131
|
+
its(:negated_actions) { is_expected.to eq %i(new create) }
|
132
|
+
end
|
133
|
+
|
134
|
+
context "except_to_update" do
|
135
|
+
let(:name) { :except_to_update }
|
136
|
+
|
137
|
+
its(:behavior) { is_expected.to eq :update }
|
138
|
+
its(:actions) { is_expected.to eq %i(index show new create destroy) }
|
139
|
+
its(:negated_actions) { is_expected.to eq %i(edit update) }
|
140
|
+
end
|
141
|
+
|
142
|
+
context "except_to_delete" do
|
143
|
+
let(:name) { :except_to_delete }
|
144
|
+
|
145
|
+
its(:behavior) { is_expected.to eq :delete }
|
146
|
+
its(:actions) { is_expected.to eq %i(index show new create edit update) }
|
147
|
+
its(:negated_actions) { is_expected.to eq %i(destroy) }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|