resource_policy 1.0.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 +7 -0
- data/.github/workflows/ruby.yml +20 -0
- data/.gitignore +15 -0
- data/.hound.yml +3 -0
- data/.rspec +3 -0
- data/.rubocop.yml +43 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +19 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +232 -0
- data/LICENSE.txt +21 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/.nojekyll +0 -0
- data/docs/README.md +163 -0
- data/docs/_sidebar.md +6 -0
- data/docs/components/action_validator.md +34 -0
- data/docs/components/actions_policy.md +68 -0
- data/docs/components/attributes_policy.md +68 -0
- data/docs/components/policy.md +202 -0
- data/docs/index.html +70 -0
- data/lib/resource_policy/policy/action_policy_configuration.rb +37 -0
- data/lib/resource_policy/policy/actions_policy/action_policy.rb +32 -0
- data/lib/resource_policy/policy/actions_policy/actions_policy_model.rb +39 -0
- data/lib/resource_policy/policy/actions_policy.rb +35 -0
- data/lib/resource_policy/policy/attributes_policy/attribute_configuration.rb +72 -0
- data/lib/resource_policy/policy/attributes_policy/attribute_policy.rb +49 -0
- data/lib/resource_policy/policy/attributes_policy/attributes_policy_model.rb +52 -0
- data/lib/resource_policy/policy/attributes_policy.rb +58 -0
- data/lib/resource_policy/policy/merge_policies.rb +44 -0
- data/lib/resource_policy/policy/policy_configuration.rb +87 -0
- data/lib/resource_policy/policy.rb +31 -0
- data/lib/resource_policy/protected_resource.rb +43 -0
- data/lib/resource_policy/rails.rb +5 -0
- data/lib/resource_policy/validators/action_policy_validator.rb +54 -0
- data/lib/resource_policy/version.rb +5 -0
- data/lib/resource_policy.rb +11 -0
- data/resource_policy.gemspec +47 -0
- metadata +212 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
# ResourcePolicy::ActionValidator
|
2
|
+
|
3
|
+
The `ResourcePolicy::ActionValidator` is a validator used to check the policy of a resource before performing a certain action. It helps to ensure that a user is only allowed to perform actions on a resource that they have permission to do so.
|
4
|
+
|
5
|
+
## Options
|
6
|
+
|
7
|
+
The `ResourcePolicy::ActionValidator` accepts two options:
|
8
|
+
|
9
|
+
- `:allowed_to` (required) - Specifies the action type that needs to be checked. This can be a symbol or a string.
|
10
|
+
- `:as` (optional) - Specifies the key that will be used to display errors. This is useful if you want to rename the attribute being validated.
|
11
|
+
|
12
|
+
## Usage Example
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
require 'resource_policy/action_validator'
|
16
|
+
|
17
|
+
class SomeClass
|
18
|
+
validates :some_policy, 'resource_policy/action': { allowed_to: :create, as: :some_item }
|
19
|
+
|
20
|
+
def some_policy
|
21
|
+
SomePolicy.new
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
some_object = SomeClass.new
|
26
|
+
|
27
|
+
if some_object.valid?
|
28
|
+
# No validation errors, continue with the process
|
29
|
+
else
|
30
|
+
some_object.errors.messages # => { some_item: ['action "create" is not allowed'] }
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
In this example, the `SomeClass` has an attribute named `some_policy` which is being validated using the `ResourcePolicy::ActionValidator`. The validator checks if the create action is allowed using the SomePolicy object. If the action is not allowed, an error message will be added to the `record.errors` object with the key `:some_item`. If the `:as` option is not provided, the key used to display the error will be the name of the attribute being validated.
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# ResourcePolicy::ActionsPolicy
|
2
|
+
|
3
|
+
## policy#action
|
4
|
+
|
5
|
+
Actions policy allows you to define config for each action.
|
6
|
+
|
7
|
+
Using `action` and `allowed` methods chain you can define conditions for each action:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class UserPolicy
|
11
|
+
include ResourcePolicy::Policy
|
12
|
+
|
13
|
+
policy do |c|
|
14
|
+
c.action(:read).allowed
|
15
|
+
c.action(:write).allowed(if: %i[admin? admin?])
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(user, current_user)
|
19
|
+
@user, @current_user = user, current_user
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def user_itself?
|
25
|
+
@user == @current_user
|
26
|
+
end
|
27
|
+
|
28
|
+
def admin?
|
29
|
+
@current_user.admin?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
If no condition is given then action will be always allowed.
|
35
|
+
|
36
|
+
This config means:
|
37
|
+
|
38
|
+
* `read` action is always allowed;
|
39
|
+
* `write` action is allowed only if both `admin?` and `writable?` methods returns `true`.
|
40
|
+
|
41
|
+
|
42
|
+
### policy#group
|
43
|
+
|
44
|
+
Sometimes you might have action groups which share same conditions. In this case you can group then using `#group` method:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
class UserPolicy
|
48
|
+
include ResourcePolicy::Policy
|
49
|
+
|
50
|
+
policy do |c|
|
51
|
+
c.group(:user_itself?) do |g|
|
52
|
+
g.allowed_to(:change_password)
|
53
|
+
g.allowed_to(:destroy, if: :admin?)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def admin?
|
60
|
+
...
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
In this case:
|
66
|
+
|
67
|
+
* `change_password` will be allowed if `user_itself?` returns `true`;
|
68
|
+
* `destroy` will be allowed if both `user_itself?` and ``admin?` returns `true`.
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# ResourcePolicy::AttributesPolicy
|
2
|
+
|
3
|
+
|
4
|
+
### policy#attribute
|
5
|
+
|
6
|
+
Attributes policy allows you to define separate attributes.
|
7
|
+
|
8
|
+
Using `attribute#allowed` method you can define conditions for each attribute and for each action type like `read`, `write` and etc:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
class UserPolicy
|
12
|
+
include ResourcePolicy::AttributesPolicy
|
13
|
+
|
14
|
+
policy do |c|
|
15
|
+
c.attribute(:first_name)
|
16
|
+
.allowed(:read)
|
17
|
+
.allowed(:write, if: %i[admin? writable?])
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def admin?
|
23
|
+
...
|
24
|
+
end
|
25
|
+
|
26
|
+
def writable?
|
27
|
+
...
|
28
|
+
end
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
If no condition is given then action will be always allowed for given attribute.
|
33
|
+
|
34
|
+
### attributes_policy#group
|
35
|
+
|
36
|
+
Sometimes you might have attribute groups which share same conditions. In this case you can group then using `#group` method:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class UserPolicy
|
40
|
+
include ResourcePolicy::AttributesPolicy
|
41
|
+
|
42
|
+
group(:user_itself?) do |c|
|
43
|
+
c.attribute(:password).allowed(:write)
|
44
|
+
c.attribute(:email).allowed(:read, :write)
|
45
|
+
c.attribute(:children).allowed(:read, if: :parent?)
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(user, current_user)
|
49
|
+
@user, @current_user = user, current_user
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def user_itself?
|
55
|
+
@user == @current_user
|
56
|
+
end
|
57
|
+
|
58
|
+
def parent?
|
59
|
+
@user.parent?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
In this case:
|
65
|
+
|
66
|
+
* `password` will be allowed to `write` only if `user_itself?` returns `true`.
|
67
|
+
* `email` will be allowed to `read` and `write` only if `user_itself?` returns `true`.
|
68
|
+
* `children` will be allowed to `read` if both `user_itself?` **and** `parent?` returns `true`.
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# ResourcePolicy::Policy
|
2
|
+
|
3
|
+
`Policy` includes both `AttributesPolicy` and `ActionsPolicy` modules. Their features are described separately, so read more there if you need more info.
|
4
|
+
|
5
|
+
## Policy Configuration
|
6
|
+
|
7
|
+
Here is and example how policy looks like:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class UserPolicy
|
11
|
+
include ResourcePolicy::Policy
|
12
|
+
|
13
|
+
policy do |c|
|
14
|
+
c.policy_target :user
|
15
|
+
|
16
|
+
c.action(:read).allowed(if: :readable?)
|
17
|
+
|
18
|
+
c.attribute(:first_name)
|
19
|
+
.allowed(:read, if: :readable?)
|
20
|
+
.allowed(:write, if: :writable?)
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(user, current_user:)
|
24
|
+
@user = user
|
25
|
+
@current_user = current_user
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def readable?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def writable?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
### policy#group
|
41
|
+
|
42
|
+
Sometimes you might have action groups which share same conditions. In this case you can group then using `#group` method:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
class UserPolicy
|
46
|
+
include ResourcePolicy::Policy
|
47
|
+
|
48
|
+
policy do |c|
|
49
|
+
c.group(:user_itself?) do |g|
|
50
|
+
g.action(:change_password).allowed
|
51
|
+
g.action(:destroy).allowed(if: :admin?)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def admin?
|
58
|
+
...
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
In this case:
|
64
|
+
|
65
|
+
* `change_password` will be allowed if `user_itself?` returns `true`;
|
66
|
+
* `destroy` will be allowed if both `user_itself?` and ``admin?` returns `true`.
|
67
|
+
|
68
|
+
|
69
|
+
## Usage of Policy#action
|
70
|
+
|
71
|
+
Suppose we have policy like this:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
class UserPolicy
|
75
|
+
include ResourcePolicy::Policy
|
76
|
+
|
77
|
+
policy do |c|
|
78
|
+
c.action(:read).allowed # current_user can always see user
|
79
|
+
c.action(:write).allowed(if: :admin?) # only admin current_user can update user
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize(user, current_user:)
|
83
|
+
@user = user
|
84
|
+
@current_user = current_user
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def admin?
|
90
|
+
@current_user.admin?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
then we can check each action like this:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
policy = UserPolicy.new(user, current_user: current_user)
|
99
|
+
policy.action(:read).allowed? # => true
|
100
|
+
policy.action(:write).allowed? # ... depends on `admin?` result
|
101
|
+
```
|
102
|
+
|
103
|
+
## Policy#actions_policy
|
104
|
+
|
105
|
+
Another way to check each action is to use `actions_policy` object like this:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
policy = UserPolicy.new(user, current_user: current_user)
|
109
|
+
actions_policy = policy.actions_policy
|
110
|
+
actions_policy.read.allowed? # => true
|
111
|
+
actions_policy.write.allowed? # ... depends on `admin?` result
|
112
|
+
```
|
113
|
+
|
114
|
+
## Usage of Policy#attribute
|
115
|
+
|
116
|
+
Suppose we have policy like this:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
class UserPolicy
|
120
|
+
include ResourcePolicy::Policy
|
121
|
+
|
122
|
+
policy do |c|
|
123
|
+
c.attribute(:email)
|
124
|
+
.allowed(:read) # current_user can always view user.email
|
125
|
+
.allowed(:write, if: :admin?) # only admin current_user can change email
|
126
|
+
end
|
127
|
+
|
128
|
+
def initialize(user, current_user:)
|
129
|
+
@user = user
|
130
|
+
@current_user = current_user
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def admin?
|
136
|
+
@current_user.admin?
|
137
|
+
end
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
141
|
+
then we can check each attribute like this:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
policy = UserPolicy.new(user, current_user: current_user)
|
145
|
+
policy.attribute(:email).allowed_to?(:change) # => false - no such rule
|
146
|
+
policy.attribute(:email).readable? # => true
|
147
|
+
policy.attribute(:email).writable? # ... depends on `admin?` result
|
148
|
+
```
|
149
|
+
|
150
|
+
## Usage of Policy#attributes_policy
|
151
|
+
|
152
|
+
Another way to check each action is to use `attributes_policy` object like this:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
policy = UserPolicy.new(user, current_user: current_user)
|
156
|
+
attributes_policy = policy.attributes_policy
|
157
|
+
|
158
|
+
attributes_policy.email.allowed_to?(:change) # => false - no such rule
|
159
|
+
attributes_policy.email.readable? # same as `allowed_to?(:read)`
|
160
|
+
attributes_policy.email.writable? # same as `allowed_to?(:write)`
|
161
|
+
```
|
162
|
+
|
163
|
+
## Usage of Policy#protected_resource
|
164
|
+
|
165
|
+
Policy provides `#protected_resource` method which returns wrapped model instance and does not allow to view fields which current_user does not have access to. You must define `policy_target` in order to be able to use `protected_resource` feature
|
166
|
+
|
167
|
+
Usage example:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
class UserPolicy
|
171
|
+
include ResourcePolicy::Policy
|
172
|
+
|
173
|
+
policy do |c|
|
174
|
+
c.policy_target(:user) # method name which returns target
|
175
|
+
c.attribute(:id).allowed(:read) # visible to all
|
176
|
+
c.attribute(:salary).allowed(:read, if: :admin?) # only visible to admin
|
177
|
+
end
|
178
|
+
|
179
|
+
def initialize(user, current_user:)
|
180
|
+
@user = user
|
181
|
+
@current_user = current_user
|
182
|
+
end
|
183
|
+
|
184
|
+
def admin?
|
185
|
+
@current_user.admin?
|
186
|
+
end
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
190
|
+
Now you can protect `user` like this:
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
current_user.admin? #=> false
|
194
|
+
|
195
|
+
user = User.find(1337)
|
196
|
+
user.id #=> 1337
|
197
|
+
user.email #=> "john.doe@example.com"
|
198
|
+
|
199
|
+
policy = UserPolicy.new(user, current_user: current_user)
|
200
|
+
policy.protected_resource.id #=> 1337
|
201
|
+
policy.protected_resource.email # nil
|
202
|
+
```
|
data/docs/index.html
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<title>Document</title>
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
7
|
+
<meta name="description" content="Description">
|
8
|
+
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
9
|
+
<link rel="stylesheet" href="https://unpkg.com/docsify/lib/themes/vue.css">
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
<div id="app"></div>
|
13
|
+
<script>
|
14
|
+
function parseQueryString (queryString) {
|
15
|
+
var params = {};
|
16
|
+
var temp;
|
17
|
+
// Split into key/value pairs
|
18
|
+
queries = queryString.split("&");
|
19
|
+
// Convert the array of strings into an object
|
20
|
+
for (var i = 0, l = queries.length; i < l; i++ ) {
|
21
|
+
temp = queries[i].split('=');
|
22
|
+
params[temp[0]] = temp[1];
|
23
|
+
}
|
24
|
+
return params;
|
25
|
+
};
|
26
|
+
|
27
|
+
function getJsonFromUrl() {
|
28
|
+
return parseQueryString(location.search.substr(1));
|
29
|
+
}
|
30
|
+
|
31
|
+
window.$docsify = {
|
32
|
+
auto2top: true,
|
33
|
+
name: 'ResourcePolicy',
|
34
|
+
repo: 'https://github.com/samesystem/resource_policy',
|
35
|
+
subMaxLevel: 3,
|
36
|
+
loadSidebar: true,
|
37
|
+
formatUpdated: '{MM}/{DD} {HH}:{mm}',
|
38
|
+
branchBasePath: 'https://raw.githubusercontent.com/samesystem/resource_policy/',
|
39
|
+
plugins: [
|
40
|
+
function (hook, vm) { // reasign any config value by param attribute
|
41
|
+
Object.assign(window.$docsify, getJsonFromUrl());
|
42
|
+
},
|
43
|
+
|
44
|
+
function (hook, vm) { // allow to change branch
|
45
|
+
if (!window.$docsify.branchBasePath || !window.$docsify.branch) {
|
46
|
+
return;
|
47
|
+
}
|
48
|
+
|
49
|
+
var branch = window.$docsify.branch;
|
50
|
+
var basePath = window.$docsify.branchBasePath + branch;
|
51
|
+
window.$docsify.basePath = basePath;
|
52
|
+
},
|
53
|
+
|
54
|
+
function (hook, vm) { // add edit page link
|
55
|
+
hook.beforeEach(function (html) {
|
56
|
+
var branch = window.$docsify.branch || 'master'
|
57
|
+
var url = 'https://github.com/samesystem/resource_policy/edit/' + branch + '/docs/' + vm.route.file
|
58
|
+
var editHtml = '[:memo: Edit Document](' + url + ')\n'
|
59
|
+
return html
|
60
|
+
+ '\n\n----\n\n'
|
61
|
+
+ editHtml
|
62
|
+
})
|
63
|
+
}
|
64
|
+
]
|
65
|
+
}
|
66
|
+
</script>
|
67
|
+
<script src="https://unpkg.com/docsify/lib/docsify.js"></script>
|
68
|
+
<script src="https://unpkg.com/docsify/lib/plugins/search.min.js"></script>
|
69
|
+
</body>
|
70
|
+
</html>
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ResourcePolicy
|
4
|
+
module Policy
|
5
|
+
# @private
|
6
|
+
#
|
7
|
+
# Stores configuration for action policy.
|
8
|
+
class ActionPolicyConfiguration
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def initialize(name, policy_configuration:)
|
12
|
+
@name = name.to_sym
|
13
|
+
@policy_configuration = policy_configuration
|
14
|
+
@extra_conditions = []
|
15
|
+
@configured = false
|
16
|
+
end
|
17
|
+
|
18
|
+
def allowed(options = {})
|
19
|
+
@extra_conditions = (@extra_conditions + Array(options[:if])).uniq
|
20
|
+
@configured = true
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def conditions
|
25
|
+
policy_configuration.group_conditions + @extra_conditions
|
26
|
+
end
|
27
|
+
|
28
|
+
def configured?
|
29
|
+
@configured
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :policy_configuration
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ResourcePolicy
|
4
|
+
module Policy
|
5
|
+
module ActionsPolicy
|
6
|
+
# Contains information about single action
|
7
|
+
class ActionPolicy
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
def initialize(name, policy:)
|
11
|
+
@name = name.to_sym
|
12
|
+
@policy = policy
|
13
|
+
end
|
14
|
+
|
15
|
+
def allowed?
|
16
|
+
return @allowed if defined?(@allowed)
|
17
|
+
|
18
|
+
conditions = policy_config.action(name).conditions
|
19
|
+
@allowed = conditions.all? { |condition| policy.send(condition) }
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :policy
|
25
|
+
|
26
|
+
def policy_config
|
27
|
+
policy.class.policy
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ResourcePolicy
|
4
|
+
module Policy
|
5
|
+
module ActionsPolicy
|
6
|
+
# Class which isolates methods defined via actions_policy config
|
7
|
+
class ActionsPolicyModel
|
8
|
+
require 'resource_policy/policy/actions_policy/action_policy'
|
9
|
+
|
10
|
+
def initialize(policy)
|
11
|
+
@policy = policy
|
12
|
+
@policy_item_by_name ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def method_missing(method_name)
|
16
|
+
return super unless config.actions.key?(method_name.to_sym)
|
17
|
+
|
18
|
+
policy_item(method_name.to_sym)
|
19
|
+
end
|
20
|
+
|
21
|
+
def respond_to_missing?(method_name, *args)
|
22
|
+
config.actions.key?(method_name.to_sym) || super
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :policy
|
28
|
+
|
29
|
+
def config
|
30
|
+
policy.class.policy
|
31
|
+
end
|
32
|
+
|
33
|
+
def policy_item(name)
|
34
|
+
@policy_item_by_name[name] ||= ActionPolicy.new(name, policy: policy)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ResourcePolicy
|
4
|
+
module Policy
|
5
|
+
# Allows to define actions policy using configuration block.
|
6
|
+
#
|
7
|
+
# Usage example:
|
8
|
+
#
|
9
|
+
# class SomeModelPolicy
|
10
|
+
# include Policy::ActionsPolicy
|
11
|
+
#
|
12
|
+
# policy do |c|
|
13
|
+
# c.action(:create).allowed(if: :current_user_is_admin?)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# private
|
17
|
+
#
|
18
|
+
# def current_user_is_admin?
|
19
|
+
# current_user.admin?
|
20
|
+
# end
|
21
|
+
# ...
|
22
|
+
# end
|
23
|
+
module ActionsPolicy
|
24
|
+
require 'resource_policy/policy/actions_policy/actions_policy_model'
|
25
|
+
|
26
|
+
def actions_policy
|
27
|
+
@actions_policy ||= ActionsPolicyModel.new(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
def action(name)
|
31
|
+
actions_policy.public_send(name) if actions_policy.respond_to?(name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ResourcePolicy
|
4
|
+
module Policy
|
5
|
+
module AttributesPolicy
|
6
|
+
# @private
|
7
|
+
#
|
8
|
+
# Allows to define policy for single attribute
|
9
|
+
class AttributeConfiguration
|
10
|
+
DEFAULT_OPTIONS = { if: [] }.freeze
|
11
|
+
ALLOWED_ACTIONS = %i[read write].freeze
|
12
|
+
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
def initialize(name, policy_configuration:)
|
16
|
+
@name = name
|
17
|
+
@allowed_actions = {}
|
18
|
+
@policy_configuration = policy_configuration
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize_copy(other)
|
22
|
+
super
|
23
|
+
@allowed_actions = @allowed_actions.dup.transform_values(&:dup)
|
24
|
+
end
|
25
|
+
|
26
|
+
def allowed(*action_types, **options)
|
27
|
+
action_types.map(&:to_sym).each do |action|
|
28
|
+
allowed_actions[action] = merged_action_options(action, options)
|
29
|
+
end
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def conditions_for(action)
|
34
|
+
action_conditions = allowed_actions.fetch(action, {}).fetch(:if, [])
|
35
|
+
(action_conditions + policy_configuration.group_conditions).uniq
|
36
|
+
end
|
37
|
+
|
38
|
+
def configured?
|
39
|
+
!defined_actions.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
def defined_actions
|
43
|
+
allowed_actions.keys
|
44
|
+
end
|
45
|
+
|
46
|
+
def defined_action?(action_name)
|
47
|
+
defined_actions.include?(action_name.to_sym)
|
48
|
+
end
|
49
|
+
|
50
|
+
def merge(other)
|
51
|
+
dup.tap do |new_attribute|
|
52
|
+
other.defined_actions.each do |action|
|
53
|
+
new_attribute.allowed(action, if: other.conditions_for(action))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
attr_reader :allowed_actions, :policy_configuration
|
61
|
+
|
62
|
+
def merged_action_options(action, new_options)
|
63
|
+
previous_options = allowed_actions[action]
|
64
|
+
options = previous_options || DEFAULT_OPTIONS.dup
|
65
|
+
options[:if] += Array(new_options[:if])
|
66
|
+
options[:if].uniq!
|
67
|
+
options
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ResourcePolicy
|
4
|
+
module Policy
|
5
|
+
module AttributesPolicy
|
6
|
+
# @private
|
7
|
+
#
|
8
|
+
# Stores information about access level of single attribute.
|
9
|
+
class AttributePolicy
|
10
|
+
def initialize(attribute_config, policy:)
|
11
|
+
@policy = policy
|
12
|
+
@attribute_config = attribute_config
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
attribute_config.name
|
17
|
+
end
|
18
|
+
|
19
|
+
def readable?
|
20
|
+
allowed_to?(:read)
|
21
|
+
end
|
22
|
+
|
23
|
+
def writable?
|
24
|
+
allowed_to?(:write)
|
25
|
+
end
|
26
|
+
|
27
|
+
def allowed_to?(access_level)
|
28
|
+
@allowed_to ||= {}
|
29
|
+
level_name = access_level.to_sym
|
30
|
+
|
31
|
+
return @allowed_to[level_name] if @allowed_to.key?(level_name)
|
32
|
+
|
33
|
+
@allowed_to[level_name] = fetch_allowed_to(level_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :policy, :attribute_config
|
39
|
+
|
40
|
+
def fetch_allowed_to(access_level)
|
41
|
+
return false unless attribute_config.defined_actions.include?(access_level)
|
42
|
+
|
43
|
+
conditions = attribute_config.conditions_for(access_level)
|
44
|
+
conditions.all? { |condition| policy.send(condition) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|