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