consent 1.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +13 -6
  3. data/.rubocop.yml +17 -0
  4. data/.rubocop_todo.yml +9 -11
  5. data/Appraisals +13 -0
  6. data/Gemfile +10 -1
  7. data/Gemfile.lock +259 -0
  8. data/Rakefile +9 -3
  9. data/app/models/concerns/consent/authorizable.rb +94 -0
  10. data/app/models/consent/application_record.rb +7 -0
  11. data/app/models/consent/history.rb +21 -0
  12. data/app/models/consent/permission.rb +71 -0
  13. data/bin/console +3 -3
  14. data/config.ru +9 -0
  15. data/consent.gemspec +25 -21
  16. data/db/migrate/20211104225614_create_nitro_auth_authorization_permissions.rb +19 -0
  17. data/db/migrate/20220420135558_create_nitro_auth_authorization_histories.rb +15 -0
  18. data/doc/dependency_decisions.yml +3 -0
  19. data/docs/CHANGELOG.md +32 -0
  20. data/docs/README.md +355 -0
  21. data/gemfiles/.bundle/config +2 -0
  22. data/gemfiles/rails_6_1.gemfile +15 -0
  23. data/gemfiles/rails_6_1.gemfile.lock +287 -0
  24. data/gemfiles/rails_7_0.gemfile +15 -0
  25. data/gemfiles/rails_7_0.gemfile.lock +286 -0
  26. data/gemfiles/rails_7_1.gemfile +15 -0
  27. data/gemfiles/rails_7_1.gemfile.lock +337 -0
  28. data/lib/consent/ability.rb +113 -4
  29. data/lib/consent/dsl.rb +1 -8
  30. data/lib/consent/{railtie.rb → engine.rb} +11 -8
  31. data/lib/consent/model_additions.rb +64 -0
  32. data/lib/consent/permission_migration.rb +139 -0
  33. data/lib/consent/reloader.rb +6 -5
  34. data/lib/consent/rspec/consent_action.rb +7 -7
  35. data/lib/consent/rspec/consent_view.rb +10 -14
  36. data/lib/consent/rspec.rb +3 -3
  37. data/lib/consent/subject_coder.rb +39 -0
  38. data/lib/consent/symbol_adapter.rb +18 -0
  39. data/lib/consent/version.rb +1 -1
  40. data/lib/consent.rb +25 -13
  41. data/lib/generators/consent/permissions_generator.rb +5 -5
  42. data/mkdocs.yml +6 -0
  43. data/renovate.json +15 -2
  44. metadata +126 -37
  45. data/.rspec +0 -2
  46. data/.ruby-version +0 -1
  47. data/.travis.yml +0 -20
  48. data/LICENSE +0 -21
  49. data/README.md +0 -252
  50. data/TODO.md +0 -1
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateNitroAuthAuthorizationPermissions < ActiveRecord::Migration[5.2]
4
+ def change
5
+ create_table :"#{Consent.table_name_prefix}permissions" do |t|
6
+ t.string :subject, limit: 80
7
+ t.string :action, limit: 80
8
+ t.string :view, limit: 80
9
+ t.integer :role_id
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :"#{Consent.table_name_prefix}permissions", :role_id
15
+ add_index :"#{Consent.table_name_prefix}permissions", :subject
16
+ add_index :"#{Consent.table_name_prefix}permissions", %i[subject action],
17
+ name: :"idx_#{Consent.table_name_prefix}permissions_on_subject_and_action"
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateNitroAuthAuthorizationHistories < ActiveRecord::Migration[5.2]
4
+ def change
5
+ create_table :"#{Consent.table_name_prefix}histories" do |t|
6
+ t.string :command, limit: 6
7
+ t.string :subject, limit: 80
8
+ t.string :action, limit: 80
9
+ t.string :view, limit: 80
10
+ t.integer :role_id
11
+
12
+ t.timestamps
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ ---
2
+ - - :inherit_from
3
+ - https://raw.githubusercontent.com/powerhome/oss-guide/master/license_rules.yml
data/docs/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
1
+ ## [Unreleased]
2
+
3
+ ## [2.1.0] - 2025-02-19
4
+
5
+ - Add support to rails 7.1+ [#308](https://github.com/powerhome/power-tools/pull/308)
6
+ - removed eval_view as a legacy, unsafe concern
7
+
8
+ ## [2.0.1] - 2023-01-08
9
+
10
+ - Bugfixes, minor version bumps
11
+
12
+ ## [2.0.0] - 2022-09-02
13
+
14
+ - Support multiple versions of rails (>= 5.2.8.1)
15
+ - Consent is now an engine, and provides the ability to store and manage permissions for a Consent::Authorizable class (i.e.: Role)
16
+ - Consent::Ability is improved to support the new Permission model
17
+
18
+ ## [1.0.1] - 2021-05-10
19
+
20
+ - Improve Rspec matchers
21
+
22
+ ## [1.0.0] - 2021-04-22
23
+
24
+ - Consent released with DSL to design permissions and convenient CanCan::Ability implementation.
25
+
26
+ ## [0.6.0] - 2020-12-22
27
+ ## [0.5.2] - 2019-11-28
28
+ ## [0.5.0] - 2019-11-25
29
+ ## [0.4.3] - 2019-04-02
30
+ ## [0.4.2] - 2019-03-28
31
+ ## [0.4] - 2019-03-27
32
+ ## [0.3.1] - 2016-11-08
data/docs/README.md ADDED
@@ -0,0 +1,355 @@
1
+ # Consent [![Build Status](https://travis-ci.org/powerhome/consent.svg?branch=master)](https://travis-ci.org/powerhome/consent)
2
+
3
+ ## What is Consent
4
+
5
+ Consent makes defining permissions easier by providing a clean, concise DSL for authorization
6
+ so that all abilities do not have to be in your `Ability` class.
7
+
8
+ Also, Consent adds an `Authorizable` model, so that you can easily grant permissions to your
9
+ ActiveRecord models.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'consent'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install consent
26
+
27
+ Then, require the engine in your `application.rb`
28
+
29
+ ```ruby
30
+ require "active_record/railtie"
31
+ require "consent/engine"
32
+ ```
33
+
34
+ If you wish to use the activerecord adapter (`accessible_by` and `accessible_through`), you must load `active_record/railtie` before loading the `consent/engine`.
35
+
36
+ ### Install and run the migrations
37
+
38
+ Copy and execute the migrations:
39
+
40
+ $ rails consent_engine:install:migrations
41
+ $ rails db:migrate
42
+
43
+ This will create the `consent_histories` and `consent_permissions` tables. If you want to use a different table prefix, you should set `Consent.table_name_prefix =` before you execute the migrations. I.e.:
44
+
45
+ ```ruby
46
+ # config/initializers/consent.rb
47
+
48
+ require "consent"
49
+
50
+ Consent.table_name_prefix = "my_app_"
51
+ ```
52
+
53
+ ## Authorizable
54
+
55
+ To grant permissions, you need an authorizable model. For our example we'll call it `Role`:
56
+
57
+ ```ruby
58
+ class Role < ApplicationRecord
59
+ include ::Consent::Authorizable
60
+ end
61
+ ```
62
+
63
+ You can now grant permissions to role with `grant`, `grant_all`, and `grant_all!`:
64
+
65
+ ```ruby
66
+ role = Role.new
67
+ role.grant subject: Project, action: :update, view: :department
68
+ # OR
69
+ role.grant_all({ project: { update: :department } })
70
+ # OR
71
+ role.grant_all({ project: { update: :department } }, replace: true) # to replace everything
72
+ # OR
73
+ role.grant_all!({ project: { update: :department } }, replace: true) # to grant and save
74
+
75
+ role.permissions
76
+ => [#<Consent::Permission subject: Project, action: :update, view: :department>]
77
+ ```
78
+
79
+ In the above example, we're granting `:department` view to perform `:update` in the `Project` subject.
80
+
81
+ You can now create a `Consent::Ability` using the permissions granted to the role:
82
+
83
+ ```ruby
84
+ ability = Consent::Ability.new(user, permissions: role.permissions)
85
+ ```
86
+
87
+ ## Defining permissions and views
88
+
89
+ Generate permissions with the `consent:permissions` generator. I.e:
90
+
91
+ $ rails g consent:permissions Project "Our Projects"
92
+ create app/permissions/projects.rb
93
+ create spec/permissions/projects_spec.rb
94
+
95
+ This will generate the permission definition:
96
+
97
+ ```ruby
98
+ Consent.define Project, "Our Projects" do
99
+ #in this case, Project is the subject
100
+ # and `Our Projects` is the description that makes it clear to users
101
+ # what the subject is acting upon.
102
+
103
+ end
104
+ ```
105
+
106
+ We can now define the `:update` action and a couple of different views:
107
+
108
+ ```ruby
109
+ Consent.define Project, "Our Projects" do
110
+ view :all, "All projects"
111
+
112
+ view :department, "Projects from their department" do |user|
113
+ { department_id: user.department_id }
114
+ end
115
+
116
+ view :team, "Projects from their team" do |user|
117
+ { team_id: user.team_id }
118
+ end
119
+
120
+ action :update, views: %i[department team all]
121
+ end
122
+ ```
123
+
124
+ The `:department` view will restrict the user to projects with matching `department_id`. That
125
+ means that for `Project.accessible_by(ability, :update)`, with an ability using a User with
126
+ department_id = 13, it will run a query similar to:
127
+
128
+ ```sql
129
+ > user = User.new(department_id: 13)
130
+ > ability = Consent::Ability.new(user)
131
+ > ability.consent subject: Project, action: :update, view: :department
132
+ > Project.accessible_by(user).to_sql
133
+ "SELECT * FROM projects WHERE department_id = 1"
134
+ ```
135
+
136
+ ### Subject
137
+
138
+ The subject is the central point of a group of actions and views. It will typically
139
+ be an `ActiveRecord`, a `:symbol`, or any plain ruby class.
140
+
141
+ ### Views
142
+
143
+ Views are the rules that limit access to actions. For instance, a user may see a `Project`
144
+ from his department, but not from others. You can enforce it with a `:department` view,
145
+ as in the examples below:
146
+
147
+ ### Hash Conditions
148
+
149
+ Probably the most commonly used. When the view can be defined using a `where` scope in
150
+ an ActiveRecord context. It follows a match condition and will return all objects that meet
151
+ the criteria:
152
+
153
+ ```ruby
154
+ Consent.define Project, 'Projects' do
155
+ view :department, "User's department only" do |user|
156
+ { department_id: user.id }
157
+ end
158
+ end
159
+ ```
160
+
161
+ Although hash conditions (matching object's attributes) are recommended, the constraints can
162
+ be anything you want. Since Consent does not enforce the rules, those rules are directly given
163
+ to CanCan. Following [CanCan rules](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities%3A-Best-Practice)
164
+ for defining abilities is recommended.
165
+
166
+ ### Object Conditions
167
+
168
+ If you're not matching for equal values, then you would need to use an object condition.
169
+
170
+ If you already have an object and want to check to see whether the user has permission to view
171
+ that specific object, you would use object conditions.
172
+
173
+ If your needs can't be satisfied by hash conditions, it is recommended that a second condition
174
+ is given for constraining object instances. For example, if you want to restrict a view for smaller
175
+ volume projects:
176
+
177
+ ```ruby
178
+ Consent.define Project, 'Projects' do
179
+ view :small_volumes, "User's department only",
180
+ -> (user) {
181
+ ['amount < ?', user.volume_limit]
182
+ end,
183
+ -> (user, project) {
184
+ project.amount < user.volume_limit
185
+ }
186
+ end
187
+ ```
188
+
189
+ For object conditions, the latter argument will be the referred object, while the
190
+ first will be the context given to the [Permission](#permission) (also check
191
+ [CanCan integration](#cancan-integration)).
192
+
193
+ ### Action
194
+
195
+ An action is anything you can perform on a given subject. In the example of
196
+ Features this would look like the following using Consent's DSL:
197
+
198
+ ```ruby
199
+ Consent.define :features, 'Beta Features' do
200
+ action :beta_chat, 'Beta Chat App'
201
+ end
202
+ ```
203
+
204
+ To associate different views to the same action:
205
+
206
+ ```ruby
207
+ Consent.define Project, 'Projects' do
208
+ # returns conditions that can be used as a matcher for objects so the matcher
209
+ # can return true or false (hash version)
210
+ view :department, "User's department only" do |user|
211
+ { department_id: user.id }
212
+ end
213
+ view :future_projects, "User's department only",
214
+ # returns a condition to be applied to a collection of objects
215
+ -> (_) {
216
+ ['starts_at > ?', Date.today]
217
+ end,
218
+ # returns true/false based on a condition -- to use this, you must pass in
219
+ # an instance of an object in order to check the permission
220
+ -> (user, project) {
221
+ project.starts_at > Date.today
222
+ }
223
+
224
+ action :read, 'Read projects', views: [:department, :future_projects]
225
+ end
226
+ ```
227
+
228
+ If you have a set of actions with the same set of views, you can use a
229
+ `with_defaults` block to simplify the writing:
230
+
231
+ ```ruby
232
+ with_defaults views: [:department, :small_volumes] do
233
+ action :read, 'Read projects'
234
+ action :approve, 'Approve projects'
235
+ end
236
+ ```
237
+
238
+ ### Permission
239
+
240
+ Permission is what is granted to a role, or a user. It grants the ability to perform an *action*,
241
+ on a limited scope (*view*) of the *subject*.
242
+
243
+ ## CanCan Integration
244
+
245
+ Consent provides a [CanCan](https://github.com/CanCanCommunity/cancancan#readme) ability (Consent::Ability)
246
+ that can be initialized with a group of granted permissions. You can initialize a `Consent::Ability` with:
247
+
248
+ ```ruby
249
+ Consent::Ability.new(*context, super_user: <true|false>, apply_defaults: <true|false>, permissions: [Consent::Permission, ...])
250
+ ```
251
+
252
+ - `*context` is what is given to the view evaluating permission rules. That is typically a user;
253
+ - `super_user` makes the ability to respond to `true` to any `can?` questions, and yields no
254
+ restrictions in any `accessible_by` and `accessible_through` queries;
255
+ - `apply_defaults` grants actions with the `default_view` set automatically.
256
+ - `permissions` is a collection of permissions to grant to the user
257
+
258
+ ### Manually consent permissions
259
+
260
+ You can manually grant permissions with `consent`. You could possibly subclass
261
+ `Consent::Ability` to consent some specific permissions by default:
262
+
263
+ ```ruby
264
+ class MyAbility < Consent::Ability
265
+ def initialize(...)
266
+ super(...)
267
+
268
+ consent action: :read, subject: Project, view: :department
269
+ end
270
+ end
271
+ ```
272
+
273
+ You can also consent full-access by not specifying the view:
274
+
275
+ ```ruby
276
+ consent action: :read, subject: Project
277
+ ```
278
+
279
+ Consenting the same permission multiple times is handled as a Union by CanCanCan:
280
+
281
+ ```ruby
282
+ class MyAbility < Consent::Ability
283
+ def initialize(user)
284
+ super user
285
+
286
+ consent :read, Project, :department
287
+ consent :read, Project, :future_projects
288
+ end
289
+ end
290
+
291
+ user = User.new(department_id: 13)
292
+ ability = MyAbility.new(user)
293
+
294
+ Project.accessible_by(ability, :read).to_sql
295
+ => SELECT * FROM projects WHERE ((department_id = 13) OR (starts_at > '2021-04-06'))
296
+ ```
297
+
298
+ ## Special subject and actions
299
+
300
+ ### :manage
301
+
302
+ Whenever the `:manage` action is granted to a user through `consent` or `can`, that means that all actions in that subject are automatically granted with the same restrictions. Take the following example:
303
+
304
+ ```ruby
305
+ Consent.define User, "User permissions" do
306
+ view :all, "All users"
307
+ view :self, "Own user" do |user|
308
+ { id: user.id }
309
+ end
310
+
311
+ action :manage, "Manage users", views: %i[self all]
312
+ action :update, "Manage users"
313
+ end
314
+
315
+ > ability = Consent::Ability.new(User.new(id: 123))
316
+ > ability.consent subject: User, action: :manage, view: :self
317
+ > User.accessible_by(ability, :manage).to_sql
318
+ => "SELECT `users`.* FROM `users` WHERE `users`.`id` = 123"
319
+ > User.accessible_by(ability, :view).to_sql
320
+ => "SELECT `users`.* FROM `users` WHERE `users`.`id` = 123"
321
+ ```
322
+
323
+ ### :all
324
+
325
+ In CanCan, `:all` is a special subject which means `all subjects`. Whatever is granted to `:all` is applied to all subjects. I.e.:
326
+
327
+ ```ruby
328
+ > ability = Consent::Ability.new(User.new(id: 123))
329
+ > User.accessible_by(ability, :update).to_sql
330
+ => "SELECT `users`.* FROM `users` WHERE 1=0"
331
+ > ability.can :update, :all
332
+ > User.accessible_by(ability, :update).to_sql
333
+ => "SELECT `users`.* FROM `users`"
334
+ ```
335
+
336
+ ## Rails Integration
337
+
338
+ Consent is integrated into Rails with `Consent::Engine`. To define where
339
+ your permission files will be, use `config.consent.path`. This defaults to
340
+ `#{Rails.root}/app/permissions/` to conform to Rails' standards.
341
+
342
+ ## Development
343
+
344
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
345
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
346
+ prompt that will allow you to experiment.
347
+
348
+ To install this gem onto your local machine, run `bundle exec rake install`. To
349
+ release a new version, update the version number in `version.rb`, and then run
350
+ `bundle exec rake release`, which will create a git tag for the version, push
351
+ git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
352
+
353
+ ## Contributing
354
+
355
+ Bug reports and pull requests are welcome on GitHub at https://github.com/powerhome/consent.
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_RETRY: "1"
@@ -0,0 +1,15 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "base64"
6
+ gem "bigdecimal"
7
+ gem "logger"
8
+ gem "mutex_m"
9
+ gem "net-imap", "< 0.5.0"
10
+ gem "nokogiri", "< 1.18"
11
+ gem "rubocop-powerhome", path: "../../rubocop-powerhome"
12
+ gem "zeitwerk", "< 2.7.0"
13
+ gem "rails", "6.1.7.7"
14
+
15
+ gemspec path: "../"