resource_policy 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|