consent 0.4.2 → 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 +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +21 -0
- data/.travis.yml +8 -2
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/README.md +72 -65
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/consent.gemspec +8 -5
- data/lib/consent.rb +46 -7
- data/lib/consent/ability.rb +32 -5
- data/lib/consent/action.rb +3 -1
- data/lib/consent/dsl.rb +5 -3
- data/lib/consent/permission.rb +15 -14
- data/lib/consent/railtie.rb +16 -9
- data/lib/consent/reloader.rb +30 -0
- data/lib/consent/rspec.rb +62 -12
- data/lib/consent/subject.rb +3 -10
- data/lib/consent/version.rb +3 -1
- data/lib/consent/view.rb +5 -1
- data/lib/generators/consent/permissions_generator.rb +13 -6
- data/renovate.json +5 -0
- metadata +24 -20
- data/lib/consent/permissions.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 66e7e1705d61760713253718eabda896ffa49f25ea6e1a9a6c4fc1a188730cfb
|
4
|
+
data.tar.gz: 4dc120c6f1e89a3cb612a62cf9fdbac565e51b9fabe9d04a68c64c3d9a8fd193
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ca74d2788b689711966b38e11ce4b857009064d1fce59c2b5335adc437c1b20a9298acb61a0e71cf3aac2eef1f13f6b8b06570014938ef757374b6a4f98b9c4
|
7
|
+
data.tar.gz: 9d4e6cf5d18d0671c9aade10a33933cc93b0cb704c623d6705332de55a1a02b3952b01fdee9d357a1c0ba9f37d570910ac1fe54614087cd88c94d3d890b5761d
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2019-11-20 02:06:29 -0300 using RuboCop version 0.65.0.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 5
|
10
|
+
# Configuration parameters: CountComments, ExcludedMethods.
|
11
|
+
# ExcludedMethods: refine
|
12
|
+
Metrics/BlockLength:
|
13
|
+
Exclude:
|
14
|
+
- 'spec/**/*'
|
15
|
+
- 'lib/consent/rspec.rb'
|
16
|
+
|
17
|
+
# Offense count: 9
|
18
|
+
Style/Documentation:
|
19
|
+
Exclude:
|
20
|
+
- 'spec/**/*'
|
21
|
+
- 'test/**/*'
|
data/.travis.yml
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
sudo: false
|
2
2
|
language: ruby
|
3
3
|
rvm:
|
4
|
-
- 2.
|
5
|
-
- 2.
|
4
|
+
- 2.5.8
|
5
|
+
- 2.6.6
|
6
|
+
- 2.7.2
|
7
|
+
- 3.0.0
|
6
8
|
before_install: gem install bundler -v 1.17.3
|
9
|
+
script:
|
10
|
+
- bundle exec rubocop
|
11
|
+
- bundle exec rspec
|
7
12
|
deploy:
|
8
13
|
provider: rubygems
|
9
14
|
api_key:
|
@@ -12,3 +17,4 @@ deploy:
|
|
12
17
|
on:
|
13
18
|
tags: true
|
14
19
|
repo: powerhome/consent
|
20
|
+
ruby: 2.6.6
|
data/Gemfile
CHANGED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright Power Home Remodeling Group, LLC
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -18,21 +18,21 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
## What is Consent
|
20
20
|
|
21
|
-
Consent makes defining permissions easier by providing a clean, concise DSL for authorization
|
21
|
+
Consent makes defining permissions easier by providing a clean, concise DSL for authorization
|
22
|
+
so that all abilities do not have to be in your `Ability`
|
22
23
|
class.
|
23
24
|
|
24
|
-
Consent takes application permissions and models them so that permissions are organized and can
|
25
|
-
following models:
|
25
|
+
Consent takes application permissions and models them so that permissions are organized and can
|
26
|
+
be defined granularly. It does so using the following models:
|
26
27
|
|
27
28
|
* View: A collection of objects limited by a given condition.
|
28
29
|
* Action: An action performed on top of the objects limited by the view. For example, one user could only `:view` something, while another could `:manage` it.
|
29
30
|
* Subject: Holds the scope of the actions.
|
30
|
-
* Permission:
|
31
|
-
a view.
|
31
|
+
* Permission: The combination of a subject, an action, and a view (or full-access).
|
32
32
|
|
33
33
|
## What Consent Is Not
|
34
34
|
|
35
|
-
Consent isn't a tool to enforce permissions -- it
|
35
|
+
Consent isn't a tool to enforce permissions -- it supports CanCan(Can) for that goal.
|
36
36
|
|
37
37
|
## Subject
|
38
38
|
|
@@ -50,7 +50,8 @@ Consent.define Project, 'Our Projects' do
|
|
50
50
|
end
|
51
51
|
```
|
52
52
|
|
53
|
-
The scope is the action that's being performed on the subject. It can be anything, but will
|
53
|
+
The scope is the action that's being performed on the subject. It can be anything, but will
|
54
|
+
typically be an ActiveRecord class, a `:symbol`, or a PORO.
|
54
55
|
|
55
56
|
For instance:
|
56
57
|
|
@@ -62,16 +63,15 @@ end
|
|
62
63
|
|
63
64
|
## Views
|
64
65
|
|
65
|
-
Views are the rules that limit
|
66
|
-
|
67
|
-
|
66
|
+
Views are the rules that limit access to actions. For instance, a user may see a `Project`
|
67
|
+
from his department, but not from others. You can enforce it with a `:department` view,
|
68
|
+
as in the examples below:
|
68
69
|
|
69
70
|
### Hash Conditions
|
70
71
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
and is based off a boolean:
|
72
|
+
Probably the most commonly used. When the view can be defined using a `where` scope in
|
73
|
+
an ActiveRecord context. It follows a match condition and will return all objects that meet
|
74
|
+
the criteria:
|
75
75
|
|
76
76
|
```ruby
|
77
77
|
Consent.define Project, 'Projects' do
|
@@ -81,22 +81,21 @@ Consent.define Project, 'Projects' do
|
|
81
81
|
end
|
82
82
|
```
|
83
83
|
|
84
|
-
Although hash conditions (matching object's attributes) are recommended,
|
85
|
-
|
86
|
-
|
84
|
+
Although hash conditions (matching object's attributes) are recommended, the constraints can
|
85
|
+
be anything you want. Since Consent does not enforce the rules, those rules are directly given
|
86
|
+
to CanCan. Following [CanCan rules](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities%3A-Best-Practice)
|
87
87
|
for defining abilities is recommended.
|
88
88
|
|
89
89
|
### Object Conditions
|
90
90
|
|
91
|
-
If you're not matching for equal values, then you would need to use an object
|
92
|
-
condition, which matches data based off a range.
|
91
|
+
If you're not matching for equal values, then you would need to use an object condition.
|
93
92
|
|
94
|
-
If you already have an object and want to check to see whether the user has
|
95
|
-
|
93
|
+
If you already have an object and want to check to see whether the user has permission to view
|
94
|
+
that specific object, you would use object conditions.
|
96
95
|
|
97
|
-
If your needs can't be satisfied by hash conditions, it is recommended that a
|
98
|
-
|
99
|
-
|
96
|
+
If your needs can't be satisfied by hash conditions, it is recommended that a second condition
|
97
|
+
is given for constraining object instances. For example, if you want to restrict a view for smaller
|
98
|
+
volume projects:
|
100
99
|
|
101
100
|
```ruby
|
102
101
|
Consent.define Project, 'Projects' do
|
@@ -111,7 +110,7 @@ end
|
|
111
110
|
```
|
112
111
|
|
113
112
|
For object conditions, the latter argument will be the referred object, while the
|
114
|
-
|
113
|
+
first will be the context given to the [Permission](#permission) (also check
|
115
114
|
[CanCan integration](#cancan-integration)).
|
116
115
|
|
117
116
|
## Action
|
@@ -161,73 +160,81 @@ end
|
|
161
160
|
|
162
161
|
## Permission
|
163
162
|
|
164
|
-
A permission is what is consented to the user. It
|
163
|
+
A permission is what is consented to the user. It consentment to perform
|
165
164
|
an *action* on a limited *view* of the *subject*. It marries the three concepts
|
166
165
|
to consent an access to the user.
|
167
166
|
|
168
|
-
|
169
|
-
hash owned by a `User`, or a `Role` on an application.
|
167
|
+
## CanCan Integration
|
170
168
|
|
171
|
-
|
169
|
+
Consent provides a CanCan ability (Consent::Ability) to integrate your
|
170
|
+
permissions with frameworks like Rails. To use it with Rails check out the
|
171
|
+
example at [Ability for Other Users](https://github.com/CanCanCommunity/cancancan/wiki/Ability-for-Other-Users)
|
172
|
+
on CanCanCan's wiki.
|
173
|
+
|
174
|
+
In the ability you define the scope of the permissions. This is typically a
|
175
|
+
user:
|
172
176
|
|
173
177
|
```ruby
|
174
|
-
|
175
|
-
project: {
|
176
|
-
read: 'department',
|
177
|
-
approve: 'small_volumes'
|
178
|
-
}
|
179
|
-
}
|
178
|
+
Consent::Ability.new(user)
|
180
179
|
```
|
181
180
|
|
182
|
-
|
181
|
+
You'd more commonly define a subclass of `Consent::Ability`, and consent access
|
182
|
+
to the user by calling `consent`:
|
183
183
|
|
184
184
|
```ruby
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
185
|
+
class MyAbility < Consent::Ability
|
186
|
+
def initialize(user)
|
187
|
+
super user
|
188
|
+
|
189
|
+
consent :read, Project, :department
|
190
|
+
end
|
191
|
+
end
|
190
192
|
```
|
191
193
|
|
192
|
-
|
194
|
+
You can also consent full access by not specifying the view:
|
193
195
|
|
194
|
-
|
195
|
-
|
196
|
+
```ruby
|
197
|
+
consent :read, Project
|
198
|
+
```
|
196
199
|
|
197
|
-
|
200
|
+
If you have a somehow manageable permission, you can consent them in batch in your ability:
|
198
201
|
|
199
202
|
```ruby
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
203
|
+
class MyAbility < Consent::Ability
|
204
|
+
def initialize(user)
|
205
|
+
super user
|
206
|
+
|
207
|
+
user.permissions.each do |permission|
|
208
|
+
consent permission.action, permission.subject, permission.view
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
205
212
|
```
|
206
213
|
|
207
|
-
|
214
|
+
Consenting the same permission multiple times is handled as a Union by CanCanCan:
|
208
215
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
216
|
+
```ruby
|
217
|
+
class MyAbility < Consent::Ability
|
218
|
+
def initialize(user)
|
219
|
+
super user
|
213
220
|
|
214
|
-
|
215
|
-
|
221
|
+
consent :read, Project, :department
|
222
|
+
consent :read, Project, :future_projects
|
223
|
+
end
|
224
|
+
end
|
216
225
|
|
217
|
-
|
218
|
-
|
219
|
-
```
|
226
|
+
user = User.new(department_id: 13)
|
227
|
+
ability = MyAbility.new(user)
|
220
228
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
in the exact same order, so it's up to you to define what your context is.
|
229
|
+
Project.accessible_by(ability, :read).to_sql
|
230
|
+
=> SELECT * FROM projects WHERE ((department_id = 13) OR (starts_at > '2021-04-06'))
|
231
|
+
```
|
225
232
|
|
226
233
|
## Rails Integration
|
227
234
|
|
228
235
|
Consent is integrated into Rails with `Consent::Railtie`. To define where
|
229
236
|
your permission files will be, use `config.consent.path`. This defaults to
|
230
|
-
|
237
|
+
`#{Rails.root}/app/permissions/` to conform to Rails' standards.
|
231
238
|
|
232
239
|
## Development
|
233
240
|
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'consent'
|
5
6
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +11,5 @@ require "consent"
|
|
10
11
|
# require "pry"
|
11
12
|
# Pry.start
|
12
13
|
|
13
|
-
require
|
14
|
+
require 'irb'
|
14
15
|
IRB.start
|
data/consent.gemspec
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'consent/version'
|
5
6
|
|
@@ -12,14 +13,16 @@ Gem::Specification.new do |spec|
|
|
12
13
|
spec.summary = 'Consent'
|
13
14
|
spec.description = 'Consent'
|
14
15
|
|
16
|
+
spec.licenses = ['MIT']
|
17
|
+
|
15
18
|
spec.files = `git ls-files`.split.reject do |file|
|
16
19
|
file =~ /^(test|spec|features)/
|
17
20
|
end
|
18
21
|
spec.require_paths = ['lib']
|
19
22
|
|
20
|
-
spec.add_development_dependency '
|
23
|
+
spec.add_development_dependency 'bundler', '>= 1.17.3'
|
21
24
|
spec.add_development_dependency 'cancancan', '~> 1.15.0'
|
22
|
-
spec.add_development_dependency '
|
23
|
-
spec.add_development_dependency 'rake', '~> 10.0'
|
25
|
+
spec.add_development_dependency 'rake', '>= 12.3.3'
|
24
26
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
27
|
+
spec.add_development_dependency 'rubocop', '~> 0.65.0'
|
25
28
|
end
|
data/lib/consent.rb
CHANGED
@@ -1,30 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'consent/version'
|
2
4
|
require 'consent/subject'
|
3
5
|
require 'consent/view'
|
4
6
|
require 'consent/action'
|
5
7
|
require 'consent/dsl'
|
6
8
|
require 'consent/permission'
|
7
|
-
require 'consent/permissions'
|
8
9
|
require 'consent/ability' if defined?(CanCan)
|
9
10
|
require 'consent/railtie' if defined?(Rails)
|
10
11
|
|
12
|
+
# Consent makes defining permissions easier by providing a clean,
|
13
|
+
# concise DSL for authorization so that all abilities do not have
|
14
|
+
# to be in your `Ability` class.
|
11
15
|
module Consent
|
12
|
-
|
13
|
-
|
16
|
+
# Default views available to every permission
|
17
|
+
#
|
18
|
+
# i.e.:
|
19
|
+
# Defining a view with no conditions:
|
20
|
+
# Consent.default_views[:all] = Consent::View.new(:all, "All")
|
21
|
+
#
|
22
|
+
# @return [Hash<Symbol,Consent::View>]
|
14
23
|
def self.default_views
|
15
24
|
@default_views ||= {}
|
16
25
|
end
|
17
26
|
|
27
|
+
# Subjects defined in Consent
|
28
|
+
#
|
29
|
+
# @return [Array<Consent::Subject>]
|
18
30
|
def self.subjects
|
19
31
|
@subjects ||= []
|
20
32
|
end
|
21
33
|
|
34
|
+
# Finds all subjects defined with the given key
|
35
|
+
#
|
36
|
+
# @return [Array<Consent::Subject>]
|
22
37
|
def self.find_subjects(subject_key)
|
23
38
|
@subjects.find_all do |subject|
|
24
39
|
subject.key.eql?(subject_key)
|
25
40
|
end
|
26
41
|
end
|
27
42
|
|
43
|
+
# Finds an action within a subject context
|
44
|
+
#
|
45
|
+
# @return [Consent::Action,nil]
|
28
46
|
def self.find_action(subject_key, action_key)
|
29
47
|
Consent.find_subjects(subject_key)
|
30
48
|
.map(&:actions).flatten
|
@@ -33,18 +51,36 @@ module Consent
|
|
33
51
|
end
|
34
52
|
end
|
35
53
|
|
54
|
+
# Finds a view within a subject context
|
55
|
+
#
|
56
|
+
# @return [Consent::View,nil]
|
36
57
|
def self.find_view(subject_key, view_key)
|
37
58
|
views = Consent.find_subjects(subject_key)
|
38
|
-
.map
|
59
|
+
.map(&:views)
|
39
60
|
.reduce({}, &:merge)
|
40
61
|
views[view_key]
|
41
62
|
end
|
42
63
|
|
43
|
-
|
44
|
-
|
45
|
-
|
64
|
+
# Loads all permission (ruby) files from the given directory
|
65
|
+
# and using the given mechanism (default: :require)
|
66
|
+
#
|
67
|
+
# @param paths [Array<String,#to_s>] paths where the ruby files are located
|
68
|
+
# @param mechanism [:require,:load] mechanism to load the files
|
69
|
+
def self.load_subjects!(paths, mechanism = :require)
|
70
|
+
permission_files = paths.map { |dir| File.join(dir, '*.rb') }
|
71
|
+
Dir[*permission_files].each(&Kernel.method(mechanism))
|
46
72
|
end
|
47
73
|
|
74
|
+
# Defines a subject with the given key, label and options
|
75
|
+
#
|
76
|
+
# i.e:
|
77
|
+
# Consent.define :users, "User management" do
|
78
|
+
# view :department, "Same department only" do |user|
|
79
|
+
# { department_id: user.department_id }
|
80
|
+
# end
|
81
|
+
# action :read, "Can view users"
|
82
|
+
# action :update, "Can edit existing user", views: :department
|
83
|
+
# end
|
48
84
|
def self.define(key, label, options = {}, &block)
|
49
85
|
defaults = options.fetch(:defaults, {})
|
50
86
|
subjects << Subject.new(key, label).tap do |subject|
|
@@ -52,6 +88,9 @@ module Consent
|
|
52
88
|
end
|
53
89
|
end
|
54
90
|
|
91
|
+
# Maps a permissions hash to a Consent::Permissions
|
92
|
+
#
|
93
|
+
# @return [Consent::Permissions]
|
55
94
|
def self.permissions(permissions)
|
56
95
|
Permissions.new(permissions)
|
57
96
|
end
|
data/lib/consent/ability.rb
CHANGED
@@ -1,12 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Consent
|
4
|
+
# Defines a CanCan(Can)::Ability class based on a permissions hash
|
2
5
|
class Ability
|
3
6
|
include CanCan::Ability
|
4
7
|
|
5
|
-
def initialize(
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
def initialize(*args, apply_defaults: true)
|
9
|
+
@context = *args
|
10
|
+
apply_defaults! if apply_defaults
|
11
|
+
end
|
12
|
+
|
13
|
+
def consent(permission: nil, subject: nil, action: nil, view: nil)
|
14
|
+
permission ||= Permission.new(subject, action, view)
|
15
|
+
return unless permission.valid?
|
16
|
+
|
17
|
+
can(
|
18
|
+
permission.action_key, permission.subject_key,
|
19
|
+
permission.conditions(*@context),
|
20
|
+
&permission.object_conditions(*@context)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def apply_defaults!
|
27
|
+
Consent.subjects.each do |subject|
|
28
|
+
subject.actions.each do |action|
|
29
|
+
next unless action.default_view
|
30
|
+
|
31
|
+
consent(
|
32
|
+
subject: subject.key,
|
33
|
+
action: action.key,
|
34
|
+
view: action.default_view
|
35
|
+
)
|
36
|
+
end
|
10
37
|
end
|
11
38
|
end
|
12
39
|
end
|
data/lib/consent/action.rb
CHANGED
data/lib/consent/dsl.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Consent
|
2
|
-
class DSL
|
4
|
+
class DSL # :nodoc:
|
3
5
|
attr_reader :subject
|
4
6
|
|
5
7
|
def initialize(subject, defaults)
|
@@ -11,13 +13,13 @@ module Consent
|
|
11
13
|
DSL.build(@subject, @defaults.merge(new_defaults), &block)
|
12
14
|
end
|
13
15
|
|
14
|
-
# rubocop:disable
|
16
|
+
# rubocop:disable Lint/UnusedBlockArgument, Security/Eval
|
15
17
|
def eval_view(key, label, collection_conditions)
|
16
18
|
view key, label do |user|
|
17
19
|
eval(collection_conditions)
|
18
20
|
end
|
19
21
|
end
|
20
|
-
# rubocop:enable
|
22
|
+
# rubocop:enable Lint/UnusedBlockArgument, Security/Eval
|
21
23
|
|
22
24
|
def view(key, label, instance = nil, collection = nil, &block)
|
23
25
|
collection ||= block
|
data/lib/consent/permission.rb
CHANGED
@@ -1,29 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Consent
|
2
|
-
class Permission
|
3
|
-
|
4
|
-
@subject = subject
|
5
|
-
@action = action
|
6
|
-
@view = view
|
7
|
-
end
|
4
|
+
class Permission # :nodoc:
|
5
|
+
attr_reader :subject_key, :action_key, :view_key, :view
|
8
6
|
|
9
|
-
def subject_key
|
10
|
-
@
|
7
|
+
def initialize(subject_key, action_key, view_key = nil)
|
8
|
+
@subject_key = subject_key
|
9
|
+
@action_key = action_key
|
10
|
+
@view_key = view_key
|
11
|
+
@view = Consent.find_view(subject_key, view_key) if view_key
|
11
12
|
end
|
12
13
|
|
13
|
-
def
|
14
|
-
@action.
|
14
|
+
def action
|
15
|
+
@action ||= Consent.find_action(subject_key, action_key)
|
15
16
|
end
|
16
17
|
|
17
|
-
def
|
18
|
-
|
18
|
+
def valid?
|
19
|
+
action && (@view_key.nil? == @view.nil?)
|
19
20
|
end
|
20
21
|
|
21
22
|
def conditions(*args)
|
22
|
-
@view
|
23
|
+
@view.nil? ? nil : @view.conditions(*args)
|
23
24
|
end
|
24
25
|
|
25
26
|
def object_conditions(*args)
|
26
|
-
@view
|
27
|
+
@view.nil? ? nil : @view.object_conditions(*args)
|
27
28
|
end
|
28
29
|
end
|
29
30
|
end
|
data/lib/consent/railtie.rb
CHANGED
@@ -1,19 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'consent/reloader'
|
4
|
+
|
1
5
|
module Consent
|
2
6
|
# Plugs consent permission load to the Rails class loading cycle
|
3
7
|
class Railtie < Rails::Railtie
|
4
|
-
config.before_configuration do
|
5
|
-
default_path =
|
6
|
-
config.consent =
|
8
|
+
config.before_configuration do |app|
|
9
|
+
default_path = app.root.join('app', 'permissions')
|
10
|
+
config.consent = Consent::Reloader.new(
|
11
|
+
default_path,
|
12
|
+
ActiveSupport::Dependencies.mechanism
|
13
|
+
)
|
7
14
|
end
|
8
15
|
|
9
|
-
config.
|
10
|
-
|
11
|
-
Consent.load_subjects! Rails.application.config.consent.paths
|
16
|
+
config.after_initialize do |app|
|
17
|
+
app.config.consent.execute
|
12
18
|
end
|
13
19
|
|
14
|
-
|
15
|
-
|
16
|
-
ActiveSupport::Dependencies.autoload_paths -=
|
20
|
+
initializer 'initialize consent permissions reloading' do |app|
|
21
|
+
app.reloaders << config.consent
|
22
|
+
ActiveSupport::Dependencies.autoload_paths -= config.consent.paths
|
23
|
+
config.to_prepare { app.config.consent.execute }
|
17
24
|
end
|
18
25
|
end
|
19
26
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Consent
|
4
|
+
# Rails file reloader to detect permission changes and apply them to consent
|
5
|
+
class Reloader
|
6
|
+
attr_reader :paths
|
7
|
+
delegate :updated?, :execute, :execute_if_updated, to: :updater
|
8
|
+
|
9
|
+
def initialize(default_path, mechanism)
|
10
|
+
@paths = [default_path]
|
11
|
+
@mechanism = mechanism
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def reload!
|
17
|
+
Consent.subjects.clear
|
18
|
+
Consent.load_subjects! paths, @mechanism
|
19
|
+
end
|
20
|
+
|
21
|
+
def updater
|
22
|
+
@updater ||= ActiveSupport::FileUpdateChecker.new([], globs) { reload! }
|
23
|
+
end
|
24
|
+
|
25
|
+
def globs
|
26
|
+
pairs = paths.map { |path| [path.to_s, %w[rb]] }
|
27
|
+
Hash[pairs]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/consent/rspec.rb
CHANGED
@@ -1,6 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'consent'
|
2
4
|
|
3
5
|
module Consent
|
6
|
+
# RSpec helpers for consent. Given permissions are loaded,
|
7
|
+
# gives you the ability of defining permission specs like
|
8
|
+
#
|
9
|
+
# Given "users" permissions
|
10
|
+
# Consent.define :users, "User management" do
|
11
|
+
# view :department, "Same department only" do |user|
|
12
|
+
# { department_id: user.department_id }
|
13
|
+
# end
|
14
|
+
# action :read, "Can view users"
|
15
|
+
# action :update, "Can edit existing user", views: :department
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# RSpec.describe "User permissions" do
|
19
|
+
# include Consent::Rspec
|
20
|
+
# let(:user) { double(department_id: 15) }
|
21
|
+
#
|
22
|
+
# it do
|
23
|
+
# is_expected.to consent_view(:department, department_id: 15).to(user)
|
24
|
+
# end
|
25
|
+
# it { is_expected.to consent_action(:read) }
|
26
|
+
# it { is_expected.to consent_action(:update).with_views(:department) }
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Find more examples at:
|
30
|
+
# https://github.com/powerhome/consent
|
4
31
|
module Rspec
|
5
32
|
extend RSpec::Matchers::DSL
|
6
33
|
|
@@ -11,17 +38,25 @@ module Consent
|
|
11
38
|
|
12
39
|
match do |subject_key|
|
13
40
|
action = Consent.find_action(subject_key, action_key)
|
14
|
-
action && @views
|
41
|
+
if action && @views
|
42
|
+
values_match?(action.view_keys.sort, @views.sort)
|
43
|
+
else
|
44
|
+
!action.nil?
|
45
|
+
end
|
15
46
|
end
|
16
47
|
|
17
48
|
failure_message do |subject_key|
|
18
49
|
action = Consent.find_action(subject_key, action_key)
|
19
|
-
message =
|
20
|
-
|
21
|
-
|
50
|
+
message = format(
|
51
|
+
'expected %<skey>s (%<sclass>s) to provide action %<action>s',
|
52
|
+
skey: subject_key.to_s, sclass: subject.class, action: action_key
|
53
|
+
)
|
22
54
|
|
23
55
|
if action && @views
|
24
|
-
|
56
|
+
format(
|
57
|
+
'%<message>s with views %<views>s, but actual views are %<keys>p',
|
58
|
+
message: message, views: @views, keys: action.view_keys
|
59
|
+
)
|
25
60
|
else
|
26
61
|
message
|
27
62
|
end
|
@@ -35,21 +70,36 @@ module Consent
|
|
35
70
|
|
36
71
|
match do |subject_key|
|
37
72
|
view = Consent.find_view(subject_key, view_key)
|
38
|
-
|
73
|
+
if conditions
|
74
|
+
view&.conditions(*@context).eql?(conditions)
|
75
|
+
else
|
76
|
+
!view.nil?
|
77
|
+
end
|
39
78
|
end
|
40
79
|
|
41
80
|
failure_message do |subject_key|
|
42
81
|
view = Consent.find_view(subject_key, view_key)
|
43
|
-
message =
|
44
|
-
|
45
|
-
|
82
|
+
message = format(
|
83
|
+
'expected %<skey>s (%<sclass>s) to provide view %<view>s with` \
|
84
|
+
`%<conditions>p, but',
|
85
|
+
skey: subject_key.to_s, sclass: subject.class,
|
86
|
+
view: view_key, conditions: conditions
|
87
|
+
)
|
46
88
|
|
47
89
|
if view && conditions
|
48
90
|
actual_conditions = view.conditions(*@context)
|
49
|
-
|
91
|
+
format(
|
92
|
+
'%<message>s conditions are %<conditions>p',
|
93
|
+
message: message, conditions: actual_conditions
|
94
|
+
)
|
50
95
|
else
|
51
|
-
actual_views = Consent.find_subjects(subject_key)
|
52
|
-
|
96
|
+
actual_views = Consent.find_subjects(subject_key)
|
97
|
+
.map(&:views)
|
98
|
+
.map(&:keys).flatten
|
99
|
+
format(
|
100
|
+
'%<message>s available views are %<views>p',
|
101
|
+
message: message, views: actual_views
|
102
|
+
)
|
53
103
|
end
|
54
104
|
end
|
55
105
|
end
|
data/lib/consent/subject.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Consent
|
2
|
-
class Subject
|
4
|
+
class Subject # :nodoc:
|
3
5
|
attr_reader :key, :label, :actions, :views
|
4
6
|
|
5
7
|
def initialize(key, label)
|
@@ -8,14 +10,5 @@ module Consent
|
|
8
10
|
@actions = []
|
9
11
|
@views = Consent.default_views.clone
|
10
12
|
end
|
11
|
-
|
12
|
-
def permission_key
|
13
|
-
ActiveSupport::Inflector.underscore(@key.to_s).to_sym
|
14
|
-
end
|
15
|
-
|
16
|
-
def view_for(action, key)
|
17
|
-
view = @views.keys & action.view_keys & [key]
|
18
|
-
@views[view.first] || @views[action.default_view]
|
19
|
-
end
|
20
13
|
end
|
21
14
|
end
|
data/lib/consent/version.rb
CHANGED
data/lib/consent/view.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Consent
|
2
|
-
class View
|
4
|
+
class View # :nodoc:
|
3
5
|
attr_reader :key, :label
|
4
6
|
|
5
7
|
def initialize(key, label, instance = nil, collection = nil)
|
@@ -11,11 +13,13 @@ module Consent
|
|
11
13
|
|
12
14
|
def conditions(*args)
|
13
15
|
return @collection unless @collection.respond_to?(:call)
|
16
|
+
|
14
17
|
@collection.call(*args)
|
15
18
|
end
|
16
19
|
|
17
20
|
def object_conditions(*args)
|
18
21
|
return @instance unless @instance.respond_to?(:curry)
|
22
|
+
|
19
23
|
@instance.curry[*args]
|
20
24
|
end
|
21
25
|
end
|
@@ -1,14 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Consent
|
2
|
-
class PermissionsGenerator < Rails::Generators::NamedBase
|
3
|
-
source_root File.expand_path('
|
4
|
+
class PermissionsGenerator < Rails::Generators::NamedBase # :nodoc:
|
5
|
+
source_root File.expand_path('templates', __dir__)
|
4
6
|
argument :description, type: :string, required: false
|
5
7
|
|
6
8
|
def create_permissions
|
7
|
-
template
|
8
|
-
|
9
|
-
|
9
|
+
template(
|
10
|
+
'permissions.rb.erb',
|
11
|
+
"app/permissions/#{file_path}.rb",
|
12
|
+
assigns: { description: description }
|
13
|
+
)
|
10
14
|
|
11
|
-
template
|
15
|
+
template(
|
16
|
+
'permissions_spec.rb.erb',
|
17
|
+
"spec/permissions/#{file_path}_spec.rb"
|
18
|
+
)
|
12
19
|
end
|
13
20
|
end
|
14
21
|
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: consent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carlos Palhares
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 1.17.3
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 1.17.3
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: cancancan
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,47 +39,47 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 1.15.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 12.3.3
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 12.3.3
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '3.0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '3.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rubocop
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
75
|
+
version: 0.65.0
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
82
|
+
version: 0.65.0
|
83
83
|
description: Consent
|
84
84
|
email:
|
85
85
|
- chjunior@gmail.com
|
@@ -89,9 +89,12 @@ extra_rdoc_files: []
|
|
89
89
|
files:
|
90
90
|
- ".gitignore"
|
91
91
|
- ".rspec"
|
92
|
+
- ".rubocop.yml"
|
93
|
+
- ".rubocop_todo.yml"
|
92
94
|
- ".ruby-version"
|
93
95
|
- ".travis.yml"
|
94
96
|
- Gemfile
|
97
|
+
- LICENSE
|
95
98
|
- README.md
|
96
99
|
- Rakefile
|
97
100
|
- TODO.md
|
@@ -103,8 +106,8 @@ files:
|
|
103
106
|
- lib/consent/action.rb
|
104
107
|
- lib/consent/dsl.rb
|
105
108
|
- lib/consent/permission.rb
|
106
|
-
- lib/consent/permissions.rb
|
107
109
|
- lib/consent/railtie.rb
|
110
|
+
- lib/consent/reloader.rb
|
108
111
|
- lib/consent/rspec.rb
|
109
112
|
- lib/consent/subject.rb
|
110
113
|
- lib/consent/version.rb
|
@@ -112,8 +115,10 @@ files:
|
|
112
115
|
- lib/generators/consent/permissions_generator.rb
|
113
116
|
- lib/generators/consent/templates/permissions.rb.erb
|
114
117
|
- lib/generators/consent/templates/permissions_spec.rb.erb
|
118
|
+
- renovate.json
|
115
119
|
homepage:
|
116
|
-
licenses:
|
120
|
+
licenses:
|
121
|
+
- MIT
|
117
122
|
metadata: {}
|
118
123
|
post_install_message:
|
119
124
|
rdoc_options: []
|
@@ -130,8 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
135
|
- !ruby/object:Gem::Version
|
131
136
|
version: '0'
|
132
137
|
requirements: []
|
133
|
-
|
134
|
-
rubygems_version: 2.7.3
|
138
|
+
rubygems_version: 3.0.8
|
135
139
|
signing_key:
|
136
140
|
specification_version: 4
|
137
141
|
summary: Consent
|
data/lib/consent/permissions.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
module Consent
|
2
|
-
class Permissions
|
3
|
-
include Enumerable
|
4
|
-
|
5
|
-
def initialize(permissions)
|
6
|
-
@permissions = permissions
|
7
|
-
end
|
8
|
-
|
9
|
-
def each(&block)
|
10
|
-
Consent.subjects.each do |subject|
|
11
|
-
subject.actions.map do |action|
|
12
|
-
map_permission subject, action
|
13
|
-
end.compact.each(&block)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def map_permission(subject, action)
|
20
|
-
subject_key = subject.permission_key
|
21
|
-
actions = @permissions[subject_key] || @permissions[subject_key.to_s]
|
22
|
-
view = actions && (actions[action.key] || actions[action.key.to_s])
|
23
|
-
full(subject, action, view) || partial(subject, action, view)
|
24
|
-
end
|
25
|
-
|
26
|
-
def full(subject, action, view_key)
|
27
|
-
return unless Consent::FULL_ACCESS.include?(view_key.to_s.strip)
|
28
|
-
Permission.new(subject, action)
|
29
|
-
end
|
30
|
-
|
31
|
-
def partial(subject, action, view_key)
|
32
|
-
view = subject.view_for(action, view_key.to_s.to_sym)
|
33
|
-
return if view.nil?
|
34
|
-
Permission.new(subject, action, view)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|