simple_authorize 0.1.0 → 1.0.1
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/.overcommit.yml +55 -0
- data/.simplecov +15 -0
- data/CHANGELOG.md +113 -25
- data/CODE_OF_CONDUCT.md +129 -0
- data/CONTRIBUTING.md +182 -0
- data/LICENSE.txt +1 -1
- data/README.md +210 -15
- data/SECURITY.md +77 -0
- data/lib/generators/simple_authorize/install/install_generator.rb +1 -0
- data/lib/generators/simple_authorize/install/templates/simple_authorize.rb +8 -0
- data/lib/generators/simple_authorize/policy/policy_generator.rb +55 -0
- data/lib/generators/simple_authorize/policy/templates/policy.rb.tt +39 -0
- data/lib/generators/simple_authorize/policy/templates/policy_spec.rb.tt +73 -0
- data/lib/generators/simple_authorize/policy/templates/policy_test.rb.tt +65 -0
- data/lib/simple_authorize/configuration.rb +21 -0
- data/lib/simple_authorize/controller.rb +336 -38
- data/lib/simple_authorize/policy.rb +22 -0
- data/lib/simple_authorize/railtie.rb +20 -0
- data/lib/simple_authorize/rspec.rb +149 -0
- data/lib/simple_authorize/test_helpers.rb +115 -0
- data/lib/simple_authorize/version.rb +1 -1
- data/lib/simple_authorize.rb +6 -17
- data/spec/examples.txt +51 -0
- data/spec/rspec_matchers_spec.rb +235 -0
- data/spec/spec_helper.rb +116 -0
- metadata +53 -5
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAuthorize
|
|
4
|
+
# Test helpers for Minitest
|
|
5
|
+
# Include this module in your test cases to get assertion methods for authorization testing
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# class PostPolicyTest < ActiveSupport::TestCase
|
|
9
|
+
# include SimpleAuthorize::TestHelpers
|
|
10
|
+
#
|
|
11
|
+
# def test_admin_can_destroy
|
|
12
|
+
# policy = PostPolicy.new(admin_user, post)
|
|
13
|
+
# assert_permit_action(policy, :destroy)
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
module TestHelpers
|
|
17
|
+
# Assert that a policy permits a specific action
|
|
18
|
+
#
|
|
19
|
+
# @param policy [SimpleAuthorize::Policy] The policy instance to test
|
|
20
|
+
# @param action [Symbol, String] The action to check (e.g., :show, :update)
|
|
21
|
+
# @param message [String] Optional custom failure message
|
|
22
|
+
#
|
|
23
|
+
# @example
|
|
24
|
+
# assert_permit_action(policy, :show)
|
|
25
|
+
# assert_permit_action(policy, "update")
|
|
26
|
+
def assert_permit_action(policy, action, message = nil)
|
|
27
|
+
action_method = action.to_s.end_with?("?") ? action.to_s : "#{action}?"
|
|
28
|
+
result = policy.public_send(action_method)
|
|
29
|
+
|
|
30
|
+
message ||= "Expected #{policy.class} to permit action :#{action} but it was forbidden"
|
|
31
|
+
assert result, message
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Assert that a policy forbids a specific action
|
|
35
|
+
#
|
|
36
|
+
# @param policy [SimpleAuthorize::Policy] The policy instance to test
|
|
37
|
+
# @param action [Symbol, String] The action to check (e.g., :destroy, :update)
|
|
38
|
+
# @param message [String] Optional custom failure message
|
|
39
|
+
#
|
|
40
|
+
# @example
|
|
41
|
+
# assert_forbid_action(policy, :destroy)
|
|
42
|
+
# assert_forbid_action(policy, "update")
|
|
43
|
+
def assert_forbid_action(policy, action, message = nil)
|
|
44
|
+
action_method = action.to_s.end_with?("?") ? action.to_s : "#{action}?"
|
|
45
|
+
result = policy.public_send(action_method)
|
|
46
|
+
|
|
47
|
+
message ||= "Expected #{policy.class} to forbid action :#{action} but it was permitted"
|
|
48
|
+
assert_not result, message
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Assert that a policy permits viewing a specific attribute
|
|
52
|
+
#
|
|
53
|
+
# @param policy [SimpleAuthorize::Policy] The policy instance to test
|
|
54
|
+
# @param attribute [Symbol, String] The attribute to check (e.g., :title, :email)
|
|
55
|
+
# @param message [String] Optional custom failure message
|
|
56
|
+
#
|
|
57
|
+
# @example
|
|
58
|
+
# assert_permit_viewing(policy, :title)
|
|
59
|
+
# assert_permit_viewing(policy, "email")
|
|
60
|
+
def assert_permit_viewing(policy, attribute, message = nil)
|
|
61
|
+
result = policy.attribute_visible?(attribute)
|
|
62
|
+
|
|
63
|
+
message ||= "Expected #{policy.class} to permit viewing :#{attribute} but it was hidden"
|
|
64
|
+
assert result, message
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Assert that a policy forbids viewing a specific attribute
|
|
68
|
+
#
|
|
69
|
+
# @param policy [SimpleAuthorize::Policy] The policy instance to test
|
|
70
|
+
# @param attribute [Symbol, String] The attribute to check (e.g., :password, :secret)
|
|
71
|
+
# @param message [String] Optional custom failure message
|
|
72
|
+
#
|
|
73
|
+
# @example
|
|
74
|
+
# assert_forbid_viewing(policy, :user_id)
|
|
75
|
+
# assert_forbid_viewing(policy, "password")
|
|
76
|
+
def assert_forbid_viewing(policy, attribute, message = nil)
|
|
77
|
+
result = policy.attribute_visible?(attribute)
|
|
78
|
+
|
|
79
|
+
message ||= "Expected #{policy.class} to forbid viewing :#{attribute} but it was visible"
|
|
80
|
+
assert_not result, message
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Assert that a policy permits editing a specific attribute
|
|
84
|
+
#
|
|
85
|
+
# @param policy [SimpleAuthorize::Policy] The policy instance to test
|
|
86
|
+
# @param attribute [Symbol, String] The attribute to check (e.g., :title, :body)
|
|
87
|
+
# @param message [String] Optional custom failure message
|
|
88
|
+
#
|
|
89
|
+
# @example
|
|
90
|
+
# assert_permit_editing(policy, :title)
|
|
91
|
+
# assert_permit_editing(policy, "body")
|
|
92
|
+
def assert_permit_editing(policy, attribute, message = nil)
|
|
93
|
+
result = policy.attribute_editable?(attribute)
|
|
94
|
+
|
|
95
|
+
message ||= "Expected #{policy.class} to permit editing :#{attribute} but it was not editable"
|
|
96
|
+
assert result, message
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Assert that a policy forbids editing a specific attribute
|
|
100
|
+
#
|
|
101
|
+
# @param policy [SimpleAuthorize::Policy] The policy instance to test
|
|
102
|
+
# @param attribute [Symbol, String] The attribute to check (e.g., :id, :created_at)
|
|
103
|
+
# @param message [String] Optional custom failure message
|
|
104
|
+
#
|
|
105
|
+
# @example
|
|
106
|
+
# assert_forbid_editing(policy, :published)
|
|
107
|
+
# assert_forbid_editing(policy, "id")
|
|
108
|
+
def assert_forbid_editing(policy, attribute, message = nil)
|
|
109
|
+
result = policy.attribute_editable?(attribute)
|
|
110
|
+
|
|
111
|
+
message ||= "Expected #{policy.class} to forbid editing :#{attribute} but it was editable"
|
|
112
|
+
assert_not result, message
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
data/lib/simple_authorize.rb
CHANGED
|
@@ -5,24 +5,13 @@ require_relative "simple_authorize/version"
|
|
|
5
5
|
require_relative "simple_authorize/configuration"
|
|
6
6
|
require_relative "simple_authorize/controller"
|
|
7
7
|
require_relative "simple_authorize/policy"
|
|
8
|
+
require_relative "simple_authorize/test_helpers"
|
|
8
9
|
|
|
10
|
+
# SimpleAuthorize provides a lightweight authorization framework for Rails applications
|
|
11
|
+
# without external dependencies. It offers policy-based access control inspired by Pundit.
|
|
9
12
|
module SimpleAuthorize
|
|
10
13
|
class Error < StandardError; end
|
|
11
|
-
|
|
12
|
-
# Railtie for automatic integration with Rails
|
|
13
|
-
class Railtie < Rails::Railtie
|
|
14
|
-
initializer "simple_authorize.configure" do
|
|
15
|
-
ActiveSupport.on_load(:action_controller) do
|
|
16
|
-
# Make SimpleAuthorize::Controller available as Authorization for backwards compatibility
|
|
17
|
-
::Authorization = SimpleAuthorize::Controller unless defined?(::Authorization)
|
|
18
|
-
# Make SimpleAuthorize::Policy available as ApplicationPolicy for backwards compatibility
|
|
19
|
-
::ApplicationPolicy = SimpleAuthorize::Policy unless defined?(::ApplicationPolicy)
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Generate initializer template
|
|
24
|
-
generators do
|
|
25
|
-
require_relative "generators/simple_authorize/install/install_generator"
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
14
|
end
|
|
15
|
+
|
|
16
|
+
# Only load Railtie if Rails is defined
|
|
17
|
+
require_relative "simple_authorize/railtie" if defined?(Rails::Railtie)
|
data/spec/examples.txt
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
example_id | status | run_time |
|
|
2
|
+
-------------------------------------- | ------ | --------------- |
|
|
3
|
+
./spec/rspec_matchers_spec.rb[1:1:1:1] | passed | 0.00003 seconds |
|
|
4
|
+
./spec/rspec_matchers_spec.rb[1:1:1:2] | passed | 0.00003 seconds |
|
|
5
|
+
./spec/rspec_matchers_spec.rb[1:1:1:3] | passed | 0.00004 seconds |
|
|
6
|
+
./spec/rspec_matchers_spec.rb[1:1:2:1] | passed | 0.00004 seconds |
|
|
7
|
+
./spec/rspec_matchers_spec.rb[1:1:2:2] | passed | 0.00004 seconds |
|
|
8
|
+
./spec/rspec_matchers_spec.rb[1:1:3:1] | passed | 0.00004 seconds |
|
|
9
|
+
./spec/rspec_matchers_spec.rb[1:1:3:2] | passed | 0.00004 seconds |
|
|
10
|
+
./spec/rspec_matchers_spec.rb[1:1:4:1] | passed | 0.00003 seconds |
|
|
11
|
+
./spec/rspec_matchers_spec.rb[1:1:4:2] | passed | 0.00012 seconds |
|
|
12
|
+
./spec/rspec_matchers_spec.rb[1:2:1:1] | passed | 0.00003 seconds |
|
|
13
|
+
./spec/rspec_matchers_spec.rb[1:2:1:2] | passed | 0.00004 seconds |
|
|
14
|
+
./spec/rspec_matchers_spec.rb[1:2:2:1] | passed | 0.00004 seconds |
|
|
15
|
+
./spec/rspec_matchers_spec.rb[1:2:2:2] | passed | 0.00003 seconds |
|
|
16
|
+
./spec/rspec_matchers_spec.rb[1:2:3:1] | passed | 0.00004 seconds |
|
|
17
|
+
./spec/rspec_matchers_spec.rb[1:2:3:2] | passed | 0.00004 seconds |
|
|
18
|
+
./spec/rspec_matchers_spec.rb[1:2:4:1] | passed | 0.00003 seconds |
|
|
19
|
+
./spec/rspec_matchers_spec.rb[1:2:4:2] | passed | 0.00003 seconds |
|
|
20
|
+
./spec/rspec_matchers_spec.rb[1:3:1:1] | passed | 0.00004 seconds |
|
|
21
|
+
./spec/rspec_matchers_spec.rb[1:3:1:2] | passed | 0.00011 seconds |
|
|
22
|
+
./spec/rspec_matchers_spec.rb[1:3:1:3] | passed | 0.00005 seconds |
|
|
23
|
+
./spec/rspec_matchers_spec.rb[1:3:2:1] | passed | 0.00004 seconds |
|
|
24
|
+
./spec/rspec_matchers_spec.rb[1:3:3:1] | passed | 0.00004 seconds |
|
|
25
|
+
./spec/rspec_matchers_spec.rb[1:3:3:2] | passed | 0.00005 seconds |
|
|
26
|
+
./spec/rspec_matchers_spec.rb[1:3:4:1] | passed | 0.00003 seconds |
|
|
27
|
+
./spec/rspec_matchers_spec.rb[1:3:4:2] | passed | 0.00003 seconds |
|
|
28
|
+
./spec/rspec_matchers_spec.rb[1:4:1:1] | passed | 0.00003 seconds |
|
|
29
|
+
./spec/rspec_matchers_spec.rb[1:4:2:1] | passed | 0.00003 seconds |
|
|
30
|
+
./spec/rspec_matchers_spec.rb[1:4:2:2] | passed | 0.00004 seconds |
|
|
31
|
+
./spec/rspec_matchers_spec.rb[1:4:3:1] | passed | 0.00004 seconds |
|
|
32
|
+
./spec/rspec_matchers_spec.rb[1:4:4:1] | passed | 0.00003 seconds |
|
|
33
|
+
./spec/rspec_matchers_spec.rb[1:4:4:2] | passed | 0.00003 seconds |
|
|
34
|
+
./spec/rspec_matchers_spec.rb[1:5:1:1] | passed | 0.00003 seconds |
|
|
35
|
+
./spec/rspec_matchers_spec.rb[1:5:1:2] | passed | 0.00003 seconds |
|
|
36
|
+
./spec/rspec_matchers_spec.rb[1:5:2:1] | passed | 0.00004 seconds |
|
|
37
|
+
./spec/rspec_matchers_spec.rb[1:5:3:1] | passed | 0.00004 seconds |
|
|
38
|
+
./spec/rspec_matchers_spec.rb[1:5:3:2] | passed | 0.00003 seconds |
|
|
39
|
+
./spec/rspec_matchers_spec.rb[1:5:4:1] | passed | 0.00003 seconds |
|
|
40
|
+
./spec/rspec_matchers_spec.rb[1:5:4:2] | passed | 0.00003 seconds |
|
|
41
|
+
./spec/rspec_matchers_spec.rb[1:6:1:1] | passed | 0.00003 seconds |
|
|
42
|
+
./spec/rspec_matchers_spec.rb[1:6:2:1] | passed | 0.00003 seconds |
|
|
43
|
+
./spec/rspec_matchers_spec.rb[1:6:2:2] | passed | 0.00004 seconds |
|
|
44
|
+
./spec/rspec_matchers_spec.rb[1:6:3:1] | passed | 0.00004 seconds |
|
|
45
|
+
./spec/rspec_matchers_spec.rb[1:6:3:2] | passed | 0.00004 seconds |
|
|
46
|
+
./spec/rspec_matchers_spec.rb[1:6:4:1] | passed | 0.00003 seconds |
|
|
47
|
+
./spec/rspec_matchers_spec.rb[1:6:4:2] | passed | 0.00003 seconds |
|
|
48
|
+
./spec/rspec_matchers_spec.rb[1:7:1:1] | passed | 0.00004 seconds |
|
|
49
|
+
./spec/rspec_matchers_spec.rb[1:7:1:2] | passed | 0.00004 seconds |
|
|
50
|
+
./spec/rspec_matchers_spec.rb[1:7:1:3] | passed | 0.00005 seconds |
|
|
51
|
+
./spec/rspec_matchers_spec.rb[1:7:2:1] | passed | 0.00118 seconds |
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe SimpleAuthorize::RSpecMatchers do
|
|
6
|
+
let(:admin) { User.new(id: 1, role: :admin) }
|
|
7
|
+
let(:contributor) { User.new(id: 2, role: :contributor) }
|
|
8
|
+
let(:viewer) { User.new(id: 3, role: :viewer) }
|
|
9
|
+
let(:post) { Post.new(id: 1, user_id: 2) }
|
|
10
|
+
|
|
11
|
+
describe "permit_action matcher" do
|
|
12
|
+
context "when action is permitted" do
|
|
13
|
+
subject { PostPolicy.new(admin, post) }
|
|
14
|
+
|
|
15
|
+
it { is_expected.to permit_action(:destroy) }
|
|
16
|
+
it { is_expected.to permit_action(:update) }
|
|
17
|
+
it { is_expected.to permit_action(:show) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
context "when action is forbidden" do
|
|
21
|
+
subject { PostPolicy.new(viewer, post) }
|
|
22
|
+
|
|
23
|
+
it { is_expected.not_to permit_action(:destroy) }
|
|
24
|
+
it { is_expected.not_to permit_action(:update) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context "with string actions" do
|
|
28
|
+
subject { PostPolicy.new(admin, post) }
|
|
29
|
+
|
|
30
|
+
it { is_expected.to permit_action("show") }
|
|
31
|
+
it { is_expected.to permit_action("update") }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context "with explicit expect syntax" do
|
|
35
|
+
it "passes when action is permitted" do
|
|
36
|
+
policy = PostPolicy.new(admin, post)
|
|
37
|
+
expect(policy).to permit_action(:destroy)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "fails when action is forbidden" do
|
|
41
|
+
policy = PostPolicy.new(viewer, post)
|
|
42
|
+
expect(policy).not_to permit_action(:destroy)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe "forbid_action matcher" do
|
|
48
|
+
context "when action is forbidden" do
|
|
49
|
+
subject { PostPolicy.new(viewer, post) }
|
|
50
|
+
|
|
51
|
+
it { is_expected.to forbid_action(:destroy) }
|
|
52
|
+
it { is_expected.to forbid_action(:update) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
context "when action is permitted" do
|
|
56
|
+
subject { PostPolicy.new(admin, post) }
|
|
57
|
+
|
|
58
|
+
it { is_expected.not_to forbid_action(:show) }
|
|
59
|
+
it { is_expected.not_to forbid_action(:index) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context "with string actions" do
|
|
63
|
+
subject { PostPolicy.new(viewer, post) }
|
|
64
|
+
|
|
65
|
+
it { is_expected.to forbid_action("destroy") }
|
|
66
|
+
it { is_expected.to forbid_action("update") }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context "with explicit expect syntax" do
|
|
70
|
+
it "passes when action is forbidden" do
|
|
71
|
+
policy = PostPolicy.new(viewer, post)
|
|
72
|
+
expect(policy).to forbid_action(:destroy)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "fails when action is permitted" do
|
|
76
|
+
policy = PostPolicy.new(admin, post)
|
|
77
|
+
expect(policy).not_to forbid_action(:show)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe "permit_viewing matcher" do
|
|
83
|
+
context "when attribute is visible" do
|
|
84
|
+
subject { PostPolicy.new(viewer, post) }
|
|
85
|
+
|
|
86
|
+
it { is_expected.to permit_viewing(:title) }
|
|
87
|
+
it { is_expected.to permit_viewing(:body) }
|
|
88
|
+
it { is_expected.to permit_viewing(:id) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
context "when attribute is hidden" do
|
|
92
|
+
subject { PostPolicy.new(viewer, post) }
|
|
93
|
+
|
|
94
|
+
it { is_expected.not_to permit_viewing(:user_id) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
context "with string attributes" do
|
|
98
|
+
subject { PostPolicy.new(admin, post) }
|
|
99
|
+
|
|
100
|
+
it { is_expected.to permit_viewing("title") }
|
|
101
|
+
it { is_expected.to permit_viewing("user_id") }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
context "with explicit expect syntax" do
|
|
105
|
+
it "passes when attribute is visible" do
|
|
106
|
+
policy = PostPolicy.new(viewer, post)
|
|
107
|
+
expect(policy).to permit_viewing(:title)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "fails when attribute is hidden" do
|
|
111
|
+
policy = PostPolicy.new(viewer, post)
|
|
112
|
+
expect(policy).not_to permit_viewing(:user_id)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
describe "forbid_viewing matcher" do
|
|
118
|
+
context "when attribute is hidden" do
|
|
119
|
+
subject { PostPolicy.new(viewer, post) }
|
|
120
|
+
|
|
121
|
+
it { is_expected.to forbid_viewing(:user_id) }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
context "when attribute is visible" do
|
|
125
|
+
subject { PostPolicy.new(admin, post) }
|
|
126
|
+
|
|
127
|
+
it { is_expected.not_to forbid_viewing(:title) }
|
|
128
|
+
it { is_expected.not_to forbid_viewing(:user_id) }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
context "with string attributes" do
|
|
132
|
+
subject { PostPolicy.new(viewer, post) }
|
|
133
|
+
|
|
134
|
+
it { is_expected.to forbid_viewing("user_id") }
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
context "with explicit expect syntax" do
|
|
138
|
+
it "passes when attribute is hidden" do
|
|
139
|
+
policy = PostPolicy.new(viewer, post)
|
|
140
|
+
expect(policy).to forbid_viewing(:user_id)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "fails when attribute is visible" do
|
|
144
|
+
policy = PostPolicy.new(admin, post)
|
|
145
|
+
expect(policy).not_to forbid_viewing(:title)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
describe "permit_editing matcher" do
|
|
151
|
+
context "when attribute is editable" do
|
|
152
|
+
subject { PostPolicy.new(contributor, post) }
|
|
153
|
+
|
|
154
|
+
it { is_expected.to permit_editing(:title) }
|
|
155
|
+
it { is_expected.to permit_editing(:body) }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
context "when attribute is not editable" do
|
|
159
|
+
subject { PostPolicy.new(contributor, post) }
|
|
160
|
+
|
|
161
|
+
it { is_expected.not_to permit_editing(:published) }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
context "with string attributes" do
|
|
165
|
+
subject { PostPolicy.new(admin, post) }
|
|
166
|
+
|
|
167
|
+
it { is_expected.to permit_editing("title") }
|
|
168
|
+
it { is_expected.to permit_editing("published") }
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
context "with explicit expect syntax" do
|
|
172
|
+
it "passes when attribute is editable" do
|
|
173
|
+
policy = PostPolicy.new(contributor, post)
|
|
174
|
+
expect(policy).to permit_editing(:title)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it "fails when attribute is not editable" do
|
|
178
|
+
policy = PostPolicy.new(contributor, post)
|
|
179
|
+
expect(policy).not_to permit_editing(:published)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
describe "forbid_editing matcher" do
|
|
185
|
+
context "when attribute is not editable" do
|
|
186
|
+
subject { PostPolicy.new(contributor, post) }
|
|
187
|
+
|
|
188
|
+
it { is_expected.to forbid_editing(:published) }
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
context "when attribute is editable" do
|
|
192
|
+
subject { PostPolicy.new(admin, post) }
|
|
193
|
+
|
|
194
|
+
it { is_expected.not_to forbid_editing(:title) }
|
|
195
|
+
it { is_expected.not_to forbid_editing(:published) }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
context "with string attributes" do
|
|
199
|
+
subject { PostPolicy.new(viewer, post) }
|
|
200
|
+
|
|
201
|
+
it { is_expected.to forbid_editing("title") }
|
|
202
|
+
it { is_expected.to forbid_editing("published") }
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
context "with explicit expect syntax" do
|
|
206
|
+
it "passes when attribute is not editable" do
|
|
207
|
+
policy = PostPolicy.new(contributor, post)
|
|
208
|
+
expect(policy).to forbid_editing(:published)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it "fails when attribute is editable" do
|
|
212
|
+
policy = PostPolicy.new(admin, post)
|
|
213
|
+
expect(policy).not_to forbid_editing(:title)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
describe "edge cases" do
|
|
219
|
+
context "with nil user" do
|
|
220
|
+
subject { PostPolicy.new(nil, post) }
|
|
221
|
+
|
|
222
|
+
it { is_expected.to forbid_action(:update) }
|
|
223
|
+
it { is_expected.to forbid_viewing(:title) }
|
|
224
|
+
it { is_expected.to forbid_editing(:body) }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
context "with missing policy methods" do
|
|
228
|
+
subject { PostPolicy.new(admin, post) }
|
|
229
|
+
|
|
230
|
+
it "raises NoMethodError for nonexistent actions" do
|
|
231
|
+
expect { subject.nonexistent_action? }.to raise_error(NoMethodError)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
4
|
+
require "simple_authorize"
|
|
5
|
+
require "simple_authorize/rspec"
|
|
6
|
+
require "active_model"
|
|
7
|
+
|
|
8
|
+
# Mock objects for testing
|
|
9
|
+
class User
|
|
10
|
+
attr_accessor :id, :role
|
|
11
|
+
|
|
12
|
+
def initialize(id: 1, role: :viewer)
|
|
13
|
+
@id = id
|
|
14
|
+
@role = role
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def admin?
|
|
18
|
+
role == :admin
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def contributor?
|
|
22
|
+
role == :contributor
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def viewer?
|
|
26
|
+
role == :viewer
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def can_create_content?
|
|
30
|
+
admin? || contributor?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def can_manage_content?
|
|
34
|
+
admin?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class Post
|
|
39
|
+
attr_accessor :id, :user_id, :published
|
|
40
|
+
|
|
41
|
+
def initialize(id: 1, user_id: 1, published: true)
|
|
42
|
+
@id = id
|
|
43
|
+
@user_id = user_id
|
|
44
|
+
@published = published
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.model_name
|
|
48
|
+
ActiveModel::Name.new(self, nil, "Post")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Sample policy used across specs
|
|
53
|
+
class PostPolicy < SimpleAuthorize::Policy
|
|
54
|
+
def index?
|
|
55
|
+
true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def show?
|
|
59
|
+
true
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def create?
|
|
63
|
+
user.present? && user.can_create_content?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def update?
|
|
67
|
+
user.present? && (owner? || user.admin?)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def destroy?
|
|
71
|
+
user.present? && (owner? || user.admin?)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def publish?
|
|
75
|
+
user&.admin? || (user&.contributor? && owner?)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def visible_attributes
|
|
79
|
+
if user&.admin?
|
|
80
|
+
%i[id title body published user_id]
|
|
81
|
+
elsif user.present?
|
|
82
|
+
%i[id title body published]
|
|
83
|
+
else
|
|
84
|
+
[]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def editable_attributes
|
|
89
|
+
if user&.admin?
|
|
90
|
+
%i[title body published]
|
|
91
|
+
elsif user&.contributor?
|
|
92
|
+
%i[title body]
|
|
93
|
+
else
|
|
94
|
+
[]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
RSpec.configure do |config|
|
|
100
|
+
config.expect_with :rspec do |expectations|
|
|
101
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
config.mock_with :rspec do |mocks|
|
|
105
|
+
mocks.verify_partial_doubles = true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
|
109
|
+
config.filter_run_when_matching :focus
|
|
110
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
|
111
|
+
config.disable_monkey_patching!
|
|
112
|
+
config.warnings = true
|
|
113
|
+
config.default_formatter = "doc" if config.files_to_run.one?
|
|
114
|
+
config.order = :random
|
|
115
|
+
Kernel.srand config.seed
|
|
116
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: simple_authorize
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Scott
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: exe
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2025-11-04 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: activesupport
|
|
@@ -23,6 +24,20 @@ dependencies:
|
|
|
23
24
|
- - ">="
|
|
24
25
|
- !ruby/object:Gem::Version
|
|
25
26
|
version: '6.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: i18n
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.0'
|
|
26
41
|
- !ruby/object:Gem::Dependency
|
|
27
42
|
name: railties
|
|
28
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -51,29 +66,58 @@ dependencies:
|
|
|
51
66
|
- - ">="
|
|
52
67
|
- !ruby/object:Gem::Version
|
|
53
68
|
version: '6.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rspec
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '3.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '3.0'
|
|
54
83
|
description: SimpleAuthorize is a lightweight authorization framework for Rails that
|
|
55
84
|
provides policy-based access control, role management, and scope filtering without
|
|
56
85
|
requiring external gems. Inspired by Pundit but completely standalone.
|
|
57
86
|
email:
|
|
58
|
-
-
|
|
87
|
+
- simpleauthorize@gmail.com
|
|
59
88
|
executables: []
|
|
60
89
|
extensions: []
|
|
61
90
|
extra_rdoc_files: []
|
|
62
91
|
files:
|
|
92
|
+
- ".overcommit.yml"
|
|
93
|
+
- ".simplecov"
|
|
63
94
|
- CHANGELOG.md
|
|
95
|
+
- CODE_OF_CONDUCT.md
|
|
96
|
+
- CONTRIBUTING.md
|
|
64
97
|
- LICENSE.txt
|
|
65
98
|
- README.md
|
|
66
99
|
- Rakefile
|
|
100
|
+
- SECURITY.md
|
|
67
101
|
- lib/generators/simple_authorize/install/install_generator.rb
|
|
68
102
|
- lib/generators/simple_authorize/install/templates/README
|
|
69
103
|
- lib/generators/simple_authorize/install/templates/application_policy.rb
|
|
70
104
|
- lib/generators/simple_authorize/install/templates/simple_authorize.rb
|
|
105
|
+
- lib/generators/simple_authorize/policy/policy_generator.rb
|
|
106
|
+
- lib/generators/simple_authorize/policy/templates/policy.rb.tt
|
|
107
|
+
- lib/generators/simple_authorize/policy/templates/policy_spec.rb.tt
|
|
108
|
+
- lib/generators/simple_authorize/policy/templates/policy_test.rb.tt
|
|
71
109
|
- lib/simple_authorize.rb
|
|
72
110
|
- lib/simple_authorize/configuration.rb
|
|
73
111
|
- lib/simple_authorize/controller.rb
|
|
74
112
|
- lib/simple_authorize/policy.rb
|
|
113
|
+
- lib/simple_authorize/railtie.rb
|
|
114
|
+
- lib/simple_authorize/rspec.rb
|
|
115
|
+
- lib/simple_authorize/test_helpers.rb
|
|
75
116
|
- lib/simple_authorize/version.rb
|
|
76
117
|
- sig/simple_authorize.rbs
|
|
118
|
+
- spec/examples.txt
|
|
119
|
+
- spec/rspec_matchers_spec.rb
|
|
120
|
+
- spec/spec_helper.rb
|
|
77
121
|
homepage: https://github.com/scottlaplant/simple_authorize
|
|
78
122
|
licenses:
|
|
79
123
|
- MIT
|
|
@@ -82,6 +126,9 @@ metadata:
|
|
|
82
126
|
source_code_uri: https://github.com/scottlaplant/simple_authorize
|
|
83
127
|
changelog_uri: https://github.com/scottlaplant/simple_authorize/blob/main/CHANGELOG.md
|
|
84
128
|
bug_tracker_uri: https://github.com/scottlaplant/simple_authorize/issues
|
|
129
|
+
documentation_uri: https://github.com/scottlaplant/simple_authorize/wiki
|
|
130
|
+
rubygems_mfa_required: 'true'
|
|
131
|
+
post_install_message:
|
|
85
132
|
rdoc_options: []
|
|
86
133
|
require_paths:
|
|
87
134
|
- lib
|
|
@@ -89,14 +136,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
89
136
|
requirements:
|
|
90
137
|
- - ">="
|
|
91
138
|
- !ruby/object:Gem::Version
|
|
92
|
-
version: 3.
|
|
139
|
+
version: 3.2.0
|
|
93
140
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
141
|
requirements:
|
|
95
142
|
- - ">="
|
|
96
143
|
- !ruby/object:Gem::Version
|
|
97
144
|
version: '0'
|
|
98
145
|
requirements: []
|
|
99
|
-
rubygems_version: 3.
|
|
146
|
+
rubygems_version: 3.5.22
|
|
147
|
+
signing_key:
|
|
100
148
|
specification_version: 4
|
|
101
149
|
summary: Simple, powerful authorization for Rails without external dependencies
|
|
102
150
|
test_files: []
|