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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4cfaa01c1cf6ece41d10032df322ad2441d19ff78888b8f6821e2d149a7ee2bd
4
- data.tar.gz: 343ac5abc8e4a49bdd04f4ec2ea998ab6408fb83cd0a9b2a54ddb750bc3c494d
3
+ metadata.gz: 66e7e1705d61760713253718eabda896ffa49f25ea6e1a9a6c4fc1a188730cfb
4
+ data.tar.gz: 4dc120c6f1e89a3cb612a62cf9fdbac565e51b9fabe9d04a68c64c3d9a8fd193
5
5
  SHA512:
6
- metadata.gz: 4723a478ad632b41223aeb185dc681edf454fc7879a22c7c7267ab2e4ad53e02a44d71c1d73732c792e7c25a921545a35669695a6536f8317d430c0066121f77
7
- data.tar.gz: 6bebde1fc871b955daeb0af93547221a91551f8bda579f8986257663fdcee5175d1b318225adb1483c0e3852485185bb85d86a9ed6c2f71378c3a3e8dcc10459
6
+ metadata.gz: 9ca74d2788b689711966b38e11ce4b857009064d1fce59c2b5335adc437c1b20a9298acb61a0e71cf3aac2eef1f13f6b8b06570014938ef757374b6a4f98b9c4
7
+ data.tar.gz: 9d4e6cf5d18d0671c9aade10a33933cc93b0cb704c623d6705332de55a1a02b3952b01fdee9d357a1c0ba9f37d570910ac1fe54614087cd88c94d3d890b5761d
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ /consent-*.gem
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.2.2
5
- - 2.5.0
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in consent.gemspec
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 so that all abilities do not have to be in your `Ability`
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 be defined granularly. It does so using the
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: What is given to the user. Combines a subject, an action and
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 is intended to be used with CanCanCan and is only to make permissions more easily readable and definable.
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 typically be an ActiveRecord class, a `:symbol`, or a PORO.
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 the access to actions. For instance,
66
- a user may see a `Project` from his department, but not from others. That rule
67
- could be enforced with a `:department` view, defined like this:
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
- This is probably the most commonly used and is useful, for example,
72
- when the view can be defined using a where condition in an ActiveRecord context.
73
- It follows a match condition and will return all objects that meet the criteria
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
- the constraints can be anything you want. Since Consent does not enforce the
86
- rules, those rules are directly given to CanCan. Following [CanCan rules](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities%3A-Best-Practice)
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
- permission to view that specific object, you would use object conditions.
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
- second condition is given for constraining object instances. For example, if you
99
- want to restrict a view for smaller volume projects:
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
- former will be the context given to the [Permission](#permission) (also check
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 is the *permission* to perform
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
- A permission is not specified by the user, it is calculated from a permissions
169
- hash owned by a `User`, or a `Role` on an application.
167
+ ## CanCan Integration
170
168
 
171
- The permissions hash looks like the following:
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
- In other words:
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
- <subject>: {
187
- <action>: <view>
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
- ### Full Access
194
+ You can also consent full access by not specifying the view:
193
195
 
194
- Full (unrestricted by views) access is granted when view is `'1'`, `true` or
195
- `'true'`. For instance:
196
+ ```ruby
197
+ consent :read, Project
198
+ ```
196
199
 
197
- In other words:
200
+ If you have a somehow manageable permission, you can consent them in batch in your ability:
198
201
 
199
202
  ```ruby
200
- {
201
- projects: {
202
- approve: true
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
- ## CanCan Integration
214
+ Consenting the same permission multiple times is handled as a Union by CanCanCan:
208
215
 
209
- Consent provides a CanCan ability (Consent::Ability) to integrate your
210
- permissions with frameworks like Rails. To use it with Rails check out the
211
- example at [Ability for Other Users](https://github.com/CanCanCommunity/cancancan/wiki/Ability-for-Other-Users)
212
- on CanCanCan's wiki.
216
+ ```ruby
217
+ class MyAbility < Consent::Ability
218
+ def initialize(user)
219
+ super user
213
220
 
214
- In the ability you define the scope of the permissions. This is typically an
215
- user:
221
+ consent :read, Project, :department
222
+ consent :read, Project, :future_projects
223
+ end
224
+ end
216
225
 
217
- ```ruby
218
- Consent::Ability.new(user.permissions, user)
219
- ```
226
+ user = User.new(department_id: 13)
227
+ ability = MyAbility.new(user)
220
228
 
221
- The first parameter given to the ability is the permissions hash, seen at
222
- [Permission](#permission). The following parameters are the permission context.
223
- These parameters are given directly to the condition blocks defined by the views
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
- `app/permissions/` to conform to Rails' standards.
237
+ `#{Rails.root}/app/permissions/` to conform to Rails' standards.
231
238
 
232
239
  ## Development
233
240
 
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
data/bin/console CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "consent"
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 "irb"
14
+ require 'irb'
14
15
  IRB.start
data/consent.gemspec CHANGED
@@ -1,5 +1,6 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
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 'activesupport', '>= 4.1.11'
23
+ spec.add_development_dependency 'bundler', '>= 1.17.3'
21
24
  spec.add_development_dependency 'cancancan', '~> 1.15.0'
22
- spec.add_development_dependency 'bundler', '~> 1.17.3'
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
- FULL_ACCESS = %w(1 true).freeze
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{|subject| subject.views}
59
+ .map(&:views)
39
60
  .reduce({}, &:merge)
40
61
  views[view_key]
41
62
  end
42
63
 
43
- def self.load_subjects!(paths)
44
- permission_files = paths.map { |dir| dir.join('*.rb') }
45
- Dir[*permission_files].each(&Kernel.method(:load))
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
@@ -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(permissions, *args)
6
- Consent.permissions(permissions).each do |permission|
7
- conditions = permission.conditions(*args)
8
- object_conditions = permission.object_conditions(*args)
9
- can permission.action_key, permission.subject_key, conditions, &object_conditions
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
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Consent
2
- class Action
4
+ class Action # :nodoc:
3
5
  attr_reader :key, :label, :options
4
6
 
5
7
  def initialize(key, label, options = {})
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 Link/UnusedBlockArgument, Link/Eval
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 Link/UnusedBlockArgument, Link/Eval
22
+ # rubocop:enable Lint/UnusedBlockArgument, Security/Eval
21
23
 
22
24
  def view(key, label, instance = nil, collection = nil, &block)
23
25
  collection ||= block
@@ -1,29 +1,30 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Consent
2
- class Permission
3
- def initialize(subject, action, view = nil)
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
- @subject.key
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 action_key
14
- @action.key
14
+ def action
15
+ @action ||= Consent.find_action(subject_key, action_key)
15
16
  end
16
17
 
17
- def view_key
18
- @view && @view.key
18
+ def valid?
19
+ action && (@view_key.nil? == @view.nil?)
19
20
  end
20
21
 
21
22
  def conditions(*args)
22
- @view && @view.conditions(*args)
23
+ @view.nil? ? nil : @view.conditions(*args)
23
24
  end
24
25
 
25
26
  def object_conditions(*args)
26
- @view && @view.object_conditions(*args)
27
+ @view.nil? ? nil : @view.object_conditions(*args)
27
28
  end
28
29
  end
29
30
  end
@@ -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 = Rails.root.join('app', 'permissions')
6
- config.consent = Struct.new(:paths).new([default_path])
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.to_prepare do
10
- Consent.subjects.clear
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
- config.after_initialize do
15
- permissions_paths = config.consent.paths.map(&:to_s)
16
- ActiveSupport::Dependencies.autoload_paths -= permissions_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 ? action.view_keys.sort.eql?(@views.sort) : !action.nil?
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 = "expected %s (%s) to provide action %s" % [
20
- subject_key.to_s, subject.class, action_key
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
- '%s with views %s, but actual views are %p' % [message, @views, action.view_keys]
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
- conditions ? view.try(:conditions, *@context).eql?(conditions) : !view.nil?
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 = "expected %s (%s) to provide view %s with %p, but" % [
44
- subject_key.to_s, subject.class, view_key, conditions
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
- '%s conditions are %p' % [message, actual_conditions]
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).map(&:views).map(&:keys).flatten
52
- '%s available views are %p' % [message, actual_views]
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
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Consent
2
- VERSION = '0.4.2'.freeze
4
+ VERSION = '1.0.0'
3
5
  end
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('../templates', __FILE__)
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 "permissions.rb.erb", "app/permissions/#{file_path}.rb", assigns: {
8
- description: description
9
- }
9
+ template(
10
+ 'permissions.rb.erb',
11
+ "app/permissions/#{file_path}.rb",
12
+ assigns: { description: description }
13
+ )
10
14
 
11
- template "permissions_spec.rb.erb", "spec/permissions/#{file_path}_spec.rb"
15
+ template(
16
+ 'permissions_spec.rb.erb',
17
+ "spec/permissions/#{file_path}_spec.rb"
18
+ )
12
19
  end
13
20
  end
14
21
  end
data/renovate.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": [
3
+ "config:base"
4
+ ]
5
+ }
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.2
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: 2019-03-28 00:00:00.000000000 Z
11
+ date: 2021-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activesupport
14
+ name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 4.1.11
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: 4.1.11
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: bundler
42
+ name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 1.17.3
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: 1.17.3
54
+ version: 12.3.3
55
55
  - !ruby/object:Gem::Dependency
56
- name: rake
56
+ name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '10.0'
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: '10.0'
68
+ version: '3.0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: rspec
70
+ name: rubocop
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '3.0'
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: '3.0'
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
- rubyforge_project:
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
@@ -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