consent 1.0.1 → 2.1.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 +13 -6
- data/.rubocop.yml +17 -0
- data/.rubocop_todo.yml +9 -11
- data/Appraisals +13 -0
- data/Gemfile +10 -1
- data/Gemfile.lock +259 -0
- data/Rakefile +9 -3
- data/app/models/concerns/consent/authorizable.rb +94 -0
- data/app/models/consent/application_record.rb +7 -0
- data/app/models/consent/history.rb +21 -0
- data/app/models/consent/permission.rb +71 -0
- data/bin/console +3 -3
- data/config.ru +9 -0
- data/consent.gemspec +25 -21
- data/db/migrate/20211104225614_create_nitro_auth_authorization_permissions.rb +19 -0
- data/db/migrate/20220420135558_create_nitro_auth_authorization_histories.rb +15 -0
- data/doc/dependency_decisions.yml +3 -0
- data/docs/CHANGELOG.md +32 -0
- data/docs/README.md +355 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/rails_6_1.gemfile +15 -0
- data/gemfiles/rails_6_1.gemfile.lock +287 -0
- data/gemfiles/rails_7_0.gemfile +15 -0
- data/gemfiles/rails_7_0.gemfile.lock +286 -0
- data/gemfiles/rails_7_1.gemfile +15 -0
- data/gemfiles/rails_7_1.gemfile.lock +337 -0
- data/lib/consent/ability.rb +113 -4
- data/lib/consent/dsl.rb +1 -8
- data/lib/consent/{railtie.rb → engine.rb} +11 -8
- data/lib/consent/model_additions.rb +64 -0
- data/lib/consent/permission_migration.rb +139 -0
- data/lib/consent/reloader.rb +6 -5
- data/lib/consent/rspec/consent_action.rb +7 -7
- data/lib/consent/rspec/consent_view.rb +10 -14
- data/lib/consent/rspec.rb +3 -3
- data/lib/consent/subject_coder.rb +39 -0
- data/lib/consent/symbol_adapter.rb +18 -0
- data/lib/consent/version.rb +1 -1
- data/lib/consent.rb +25 -13
- data/lib/generators/consent/permissions_generator.rb +5 -5
- data/mkdocs.yml +6 -0
- data/renovate.json +15 -2
- metadata +126 -37
- data/.rspec +0 -2
- data/.ruby-version +0 -1
- data/.travis.yml +0 -20
- data/LICENSE +0 -21
- data/README.md +0 -252
- 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
|
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 [](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,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: "../"
|