rabarber 1.4.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -18
- data/README.md +140 -76
- data/lib/generators/rabarber/roles_generator.rb +2 -0
- data/lib/generators/rabarber/templates/create_rabarber_roles.rb.erb +3 -3
- data/lib/rabarber/audit/events/base.rb +64 -0
- data/lib/rabarber/audit/events/roles_assigned.rb +35 -0
- data/lib/rabarber/audit/events/roles_revoked.rb +35 -0
- data/lib/rabarber/audit/events/unauthorized_attempt.rb +31 -0
- data/lib/rabarber/audit/logger.rb +23 -0
- data/lib/rabarber/configuration.rb +3 -47
- data/lib/rabarber/controllers/concerns/authorization.rb +9 -11
- data/lib/rabarber/core/access.rb +5 -9
- data/lib/rabarber/core/cache.rb +42 -0
- data/lib/rabarber/core/permissions.rb +2 -0
- data/lib/rabarber/core/permissions_integrity_checker.rb +39 -0
- data/lib/rabarber/core/roleable.rb +15 -0
- data/lib/rabarber/core/rule.rb +5 -9
- data/lib/rabarber/helpers/helpers.rb +4 -2
- data/lib/rabarber/models/concerns/has_roles.rb +6 -14
- data/lib/rabarber/models/role.rb +5 -12
- data/lib/rabarber/railtie.rb +1 -7
- data/lib/rabarber/version.rb +1 -1
- data/lib/rabarber.rb +9 -9
- data/rabarber.gemspec +2 -2
- metadata +19 -7
- data/lib/rabarber/cache.rb +0 -29
- data/lib/rabarber/logger.rb +0 -40
- data/lib/rabarber/missing/actions.rb +0 -24
- data/lib/rabarber/missing/base.rb +0 -61
- data/lib/rabarber/missing/roles.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f719eb670170521e94a58a1c75d30110699ab941da6d5dae151fbf53e0eb9880
|
4
|
+
data.tar.gz: bd06646e15ab2eb5ff7b49d2a18316c4b539b95aa98bd2ec1b4de5c6b5fb921a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0db86c7d46337decbe9972ea3b0c1ad30e1c66f2b76b3ef27a494ef8c2ecc9bcf35ce3f135360e74045b7f20beb38149f5ca308af0cb3d3a0db829ff9dc7147b
|
7
|
+
data.tar.gz: d1a20c732318d12ccfe49338df7338dcf09bd1222fd574e8581d035cb38b55295e501bfa45ef63ea0a556a8c21a28abdad7d06ca765c3f14eed6e6cd98db93be
|
data/CHANGELOG.md
CHANGED
@@ -1,27 +1,53 @@
|
|
1
|
-
##
|
1
|
+
## v2.0.0
|
2
|
+
|
3
|
+
### Breaking:
|
4
|
+
|
5
|
+
- Removed `when_actions_missing` and `when_roles_missing` configuration options
|
6
|
+
- Replaced `when_unauthorized` configuration option with an overridable controller method
|
7
|
+
- Renamed `Rabarber::Role.assignees_for` method to `Rabarber::Role.assignees`
|
8
|
+
|
9
|
+
To upgrade to v2.0.0, please refer to the [migration guide](https://github.com/enjaku4/rabarber/discussions/52).
|
10
|
+
|
11
|
+
### Features:
|
12
|
+
|
13
|
+
- Added support for UUID primary keys
|
14
|
+
|
15
|
+
### Bugs:
|
16
|
+
|
17
|
+
- Fixed the issue where an error would occur if the user was not authenticated
|
18
|
+
|
19
|
+
### Misc:
|
20
|
+
|
21
|
+
- Significant refactoring and code improvements
|
22
|
+
|
23
|
+
## v1.4.1
|
24
|
+
|
25
|
+
- Fix an issue where an error could be raised when using controller-wide dynamic rules
|
26
|
+
|
27
|
+
## v1.4.0
|
2
28
|
|
3
29
|
- Add 'Audit trail' feature: Logging of role assignments, revocations, and unauthorized access attempts
|
4
30
|
- Add `audit_trail_enabled` configuration option, allowing to enable or disable the audit trail
|
5
31
|
- Deprecate `when_actions_missing` and `when_roles_missing` configuration options (see [the discussion](https://github.com/enjaku4/rabarber/discussions/48))
|
6
32
|
|
7
|
-
##
|
33
|
+
## v1.3.1
|
8
34
|
|
9
35
|
- Add `Rabarber::Role.assignees_for` method
|
10
36
|
- Fix inconsistent behavior where passing `nil` as a role name to role management methods would raise an `ActiveRecord` error instead of `Rabarber` error
|
11
37
|
- Various minor code improvements
|
12
38
|
|
13
|
-
##
|
39
|
+
## v1.3.0
|
14
40
|
|
15
41
|
- Add methods to directly add, rename, and remove roles
|
16
42
|
- Modify `Rabarber::HasRoles#assign_roles` and `Rabarber::HasRoles#revoke_roles` methods to return the list of roles assigned to the user
|
17
43
|
- Minor performance improvements
|
18
44
|
|
19
|
-
##
|
45
|
+
## v1.2.2
|
20
46
|
|
21
47
|
- Refactor to improve readability and maintainability
|
22
48
|
- Fix minor code errors
|
23
49
|
|
24
|
-
##
|
50
|
+
## v1.2.1
|
25
51
|
|
26
52
|
- Cache roles to avoid unnecessary database queries
|
27
53
|
- Introduce `cache_enabled` configuration option allowing to enable or disable role caching
|
@@ -29,61 +55,61 @@
|
|
29
55
|
- Fix an issue where an error would be raised if the user is not authenticated
|
30
56
|
- Various minor improvements
|
31
57
|
|
32
|
-
##
|
58
|
+
## v1.2.0
|
33
59
|
|
34
60
|
- Enhance handling of missing actions and roles specified in `grant_access` method by raising an error for missing actions and logging a warning for missing roles
|
35
61
|
- Introduce `when_actions_missing` and `when_roles_missing` configuration options, allowing to customize the behavior when actions or roles are not found
|
36
62
|
|
37
|
-
##
|
63
|
+
## v1.1.0
|
38
64
|
|
39
65
|
- Add support for `unless` argument in `grant_access` method, allowing to define negated dynamic rules
|
40
66
|
- Fix a bug where specifying a dynamic rule as a symbol without specifying an action would result in an error
|
41
67
|
|
42
|
-
##
|
68
|
+
## v1.0.5
|
43
69
|
|
44
70
|
- Add co-author: [trafium](https://github.com/trafium)
|
45
71
|
|
46
|
-
##
|
72
|
+
## v1.0.4
|
47
73
|
|
48
74
|
- Allow to use strings as role names
|
49
75
|
|
50
|
-
##
|
76
|
+
## v1.0.3
|
51
77
|
|
52
78
|
- Enhance clarity by improving error types and messages
|
53
79
|
- Resolve inconsistency in types of role names
|
54
80
|
|
55
|
-
##
|
81
|
+
## v1.0.2
|
56
82
|
|
57
83
|
- Various enhancements for gem development and release
|
58
84
|
- Modify `Rabarber::HasRoles#roles` method to return an array of role names instead of `Rabarber::Role` objects
|
59
85
|
|
60
|
-
##
|
86
|
+
## v1.0.1
|
61
87
|
|
62
88
|
- Various enhancements for gem development
|
63
89
|
|
64
|
-
##
|
90
|
+
## v1.0.0
|
65
91
|
|
66
92
|
- Drop support for Ruby 2.7
|
67
93
|
- Add support for Ruby 3.3
|
68
94
|
- Various minor improvements
|
69
95
|
|
70
|
-
##
|
96
|
+
## v0.1.5
|
71
97
|
|
72
98
|
- Add missing `foreign_key` option to `CreateRabarberRoles` migration
|
73
99
|
- Allow only lowercase alphanumeric characters and underscores in role names
|
74
100
|
|
75
|
-
##
|
101
|
+
## v0.1.4
|
76
102
|
|
77
103
|
- Remove `Rabarber::HasRoles#role?` method as unnecessary
|
78
104
|
|
79
|
-
##
|
105
|
+
## v0.1.3
|
80
106
|
|
81
107
|
- Fully revise and update README for clarity
|
82
108
|
|
83
|
-
##
|
109
|
+
## v0.1.2
|
84
110
|
|
85
111
|
- Fix check that `Rabarber::HasRoles` can only be included once
|
86
112
|
|
87
|
-
##
|
113
|
+
## v0.1.1
|
88
114
|
|
89
115
|
- Initial release
|
data/README.md
CHANGED
@@ -9,7 +9,7 @@ Rabarber is a role-based authorization library for Ruby on Rails, primarily desi
|
|
9
9
|
|
10
10
|
**Example of Usage**:
|
11
11
|
|
12
|
-
Consider a CRM where users with different roles have distinct access levels. For instance, the role `accountant` can interact with invoices but cannot access marketing information, while the role `marketer` has access to marketing-related data. Such authorization rules can be easily defined with Rabarber.
|
12
|
+
Consider a CRM system where users with different roles have distinct access levels. For instance, the role `accountant` can interact with invoices but cannot access marketing information, while the role `marketer` has access to marketing-related data. Such authorization rules can be easily defined with Rabarber.
|
13
13
|
|
14
14
|
---
|
15
15
|
|
@@ -21,39 +21,65 @@ class TicketsController < ApplicationController
|
|
21
21
|
|
22
22
|
grant_access action: :index, roles: :manager
|
23
23
|
def index
|
24
|
-
...
|
24
|
+
# ...
|
25
25
|
end
|
26
26
|
|
27
27
|
def delete
|
28
|
-
...
|
28
|
+
# ...
|
29
29
|
end
|
30
30
|
end
|
31
31
|
```
|
32
32
|
This means that `admin` users can access everything in `TicketsController`, while `manager` role can access only `index` action.
|
33
33
|
|
34
|
+
## Table of Contents
|
35
|
+
|
36
|
+
**Gem usage:**
|
37
|
+
- [Installation](#installation)
|
38
|
+
- [Configuration](#configuration)
|
39
|
+
- [Roles](#roles)
|
40
|
+
- [Authorization Rules](#authorization-rules)
|
41
|
+
- [Dynamic Authorization Rules](#dynamic-authorization-rules)
|
42
|
+
- [When Unauthorized](#when-unauthorized)
|
43
|
+
- [View Helpers](#view-helpers)
|
44
|
+
- [Audit Trail](#audit-trail)
|
45
|
+
|
46
|
+
**Community Resources:**
|
47
|
+
- [Problems?](#problems)
|
48
|
+
- [Contributing](#contributing)
|
49
|
+
- [Code of Conduct](#code-of-conduct)
|
50
|
+
|
51
|
+
**Legal:**
|
52
|
+
- [License](#license)
|
53
|
+
|
34
54
|
## Installation
|
35
55
|
|
36
56
|
Add the Rabarber gem to your Gemfile:
|
37
57
|
|
38
|
-
```
|
58
|
+
```rb
|
39
59
|
gem "rabarber"
|
40
60
|
```
|
41
61
|
|
42
62
|
Install the gem:
|
43
63
|
|
44
|
-
```
|
64
|
+
```shell
|
45
65
|
bundle install
|
46
66
|
```
|
47
67
|
|
48
68
|
Next, generate a migration to create tables for storing roles in the database. Make sure to specify the table name of the model representing users in your application as an argument. For instance, if the table name is `users`, run:
|
49
69
|
|
50
|
-
```
|
70
|
+
```shell
|
51
71
|
rails g rabarber:roles users
|
52
72
|
```
|
53
73
|
|
54
|
-
|
74
|
+
Rabarber supports UUIDs as primary keys. If your application uses UUIDs, add `--uuid` option to the generator:
|
55
75
|
|
76
|
+
```shell
|
77
|
+
rails g rabarber:roles users --uuid
|
56
78
|
```
|
79
|
+
|
80
|
+
Finally, run the migration to apply the changes to the database:
|
81
|
+
|
82
|
+
```shell
|
57
83
|
rails db:migrate
|
58
84
|
```
|
59
85
|
|
@@ -67,29 +93,13 @@ Rabarber.configure do |config|
|
|
67
93
|
config.cache_enabled = true
|
68
94
|
config.current_user_method = :current_user
|
69
95
|
config.must_have_roles = false
|
70
|
-
config.when_unauthorized = -> (controller) {
|
71
|
-
if controller.request.format.html?
|
72
|
-
controller.redirect_back fallback_location: controller.root_path
|
73
|
-
else
|
74
|
-
controller.head :unauthorized
|
75
|
-
end
|
76
|
-
}
|
77
96
|
end
|
78
97
|
```
|
79
98
|
|
80
|
-
- `audit_trail_enabled`
|
81
|
-
- `cache_enabled`
|
82
|
-
- `current_user_method`
|
83
|
-
- `must_have_roles`
|
84
|
-
- `when_unauthorized` must be a proc where you can define the behaviour when access is not authorized. Lambda argument `controller` is an instance of the controller where the code is executed. _By default, the user is redirected back if the request format is HTML; otherwise, a 401 Unauthorized response is sent._
|
85
|
-
|
86
|
-
### Deprecated Configuration Options
|
87
|
-
|
88
|
-
The following configuration options are deprecated and will be removed in the next major version (see [the discussion](https://github.com/enjaku4/rabarber/discussions/48)):
|
89
|
-
|
90
|
-
- `when_actions_missing` must be a proc where you can define the behaviour when the action specified in `grant_access` method cannot be found in the controller. Lambda argument `missing_actions` is an array of symbols, e.g., `[:index]`, while `context` argument is a hash that looks like this: `{ controller: "InvoicesController" }`. This check is performed when the application is initialized if `eager_load` configuration is enabled in Rails and also on every request. _By default, an error is raised when action is missing._
|
91
|
-
|
92
|
-
- `when_roles_missing` must be a proc where you can define the behaviour when the roles specified in `grant_access` method cannot be found in the database. Lambda argument `missing_roles` is an array of symbols, e.g., `[:admin]`, while `context` argument is a hash that looks like this: `{ controller: "InvoicesController", action: "index" }`. This check is performed when the application is initialized if `eager_load` configuration is enabled in Rails and also on every request. _By default, a warning is logged when roles are missing._
|
99
|
+
- `audit_trail_enabled` determines whether the audit trail functionality is enabled. The audit trail is enabled by default.
|
100
|
+
- `cache_enabled` determines whether roles are cached to avoid unnecessary database queries. Roles are cached by default. If you need to clear the cache, use `Rabarber::Cache.clear` method.
|
101
|
+
- `current_user_method` represents the method that returns the currently authenticated user. The default value is `:current_user`.
|
102
|
+
- `must_have_roles` determines whether a user with no roles can access endpoints permitted to everyone. The default value is `false` (allowing users without roles to access such endpoints).
|
93
103
|
|
94
104
|
## Roles
|
95
105
|
|
@@ -98,7 +108,7 @@ Include `Rabarber::HasRoles` module in your model representing users in your app
|
|
98
108
|
```rb
|
99
109
|
class User < ApplicationRecord
|
100
110
|
include Rabarber::HasRoles
|
101
|
-
...
|
111
|
+
# ...
|
102
112
|
end
|
103
113
|
```
|
104
114
|
|
@@ -124,7 +134,7 @@ To revoke roles, use:
|
|
124
134
|
```rb
|
125
135
|
user.revoke_roles(:accountant, :marketer)
|
126
136
|
```
|
127
|
-
If
|
137
|
+
If the user doesn't have the role you want to revoke, it will be ignored.
|
128
138
|
|
129
139
|
The method returns an array of roles assigned to the user.
|
130
140
|
|
@@ -140,7 +150,7 @@ It returns `true` if the user has at least one role and `false` otherwise.
|
|
140
150
|
|
141
151
|
**`#roles`**
|
142
152
|
|
143
|
-
To
|
153
|
+
To get all the roles assigned to the user, use:
|
144
154
|
|
145
155
|
```rb
|
146
156
|
user.roles
|
@@ -150,7 +160,7 @@ user.roles
|
|
150
160
|
|
151
161
|
To manipulate roles directly, you can use `Rabarber::Role` methods:
|
152
162
|
|
153
|
-
**`.add(
|
163
|
+
**`.add(role_name)`**
|
154
164
|
|
155
165
|
To add a new role, use:
|
156
166
|
|
@@ -167,14 +177,14 @@ To rename a role, use:
|
|
167
177
|
```rb
|
168
178
|
Rabarber::Role.rename(:admin, :administrator)
|
169
179
|
```
|
170
|
-
The first argument is the old name, and the second argument is the new name. This will rename the role and return `true`. If
|
180
|
+
The first argument is the old name, and the second argument is the new name. This will rename the role and return `true`. If the role with a new name already exists, it will return `false`.
|
171
181
|
|
172
182
|
The method won't rename the role and will return `false` if it is assigned to any user. To force the rename, use the method with `force: true` argument:
|
173
183
|
```rb
|
174
184
|
Rabarber::Role.rename(:admin, :administrator, force: true)
|
175
185
|
```
|
176
186
|
|
177
|
-
**`.remove(
|
187
|
+
**`.remove(role_name, force: false)`**
|
178
188
|
|
179
189
|
To remove a role, use:
|
180
190
|
|
@@ -197,12 +207,12 @@ If you need to list all the role names available in your application, use:
|
|
197
207
|
Rabarber::Role.names
|
198
208
|
```
|
199
209
|
|
200
|
-
**`.
|
210
|
+
**`.assignees(role_name)`**
|
201
211
|
|
202
212
|
To get all the users to whom the role is assigned, use:
|
203
213
|
|
204
214
|
```rb
|
205
|
-
Rabarber::Role.
|
215
|
+
Rabarber::Role.assignees(:admin)
|
206
216
|
```
|
207
217
|
|
208
218
|
## Authorization Rules
|
@@ -212,7 +222,7 @@ Include `Rabarber::Authorization` module into the controller that needs authoriz
|
|
212
222
|
```rb
|
213
223
|
class ApplicationController < ActionController::Base
|
214
224
|
include Rabarber::Authorization
|
215
|
-
...
|
225
|
+
# ...
|
216
226
|
end
|
217
227
|
```
|
218
228
|
This adds `.grant_access(action: nil, roles: nil, if: nil, unless: nil)` method which allows you to define the authorization rules.
|
@@ -220,19 +230,23 @@ This adds `.grant_access(action: nil, roles: nil, if: nil, unless: nil)` method
|
|
220
230
|
The most basic usage of the method is as follows:
|
221
231
|
|
222
232
|
```rb
|
223
|
-
class InvoicesController < ApplicationController
|
233
|
+
class Crm::InvoicesController < ApplicationController
|
224
234
|
grant_access action: :index, roles: [:accountant, :admin]
|
225
235
|
def index
|
226
|
-
|
236
|
+
@invoices = Invoice.all
|
237
|
+
@invoices = @invoices.paid if current_user.has_role?(:accountant)
|
238
|
+
# ...
|
227
239
|
end
|
228
240
|
|
229
|
-
grant_access action: :
|
230
|
-
def
|
231
|
-
...
|
241
|
+
grant_access action: :destroy, roles: :admin
|
242
|
+
def destroy
|
243
|
+
# ...
|
232
244
|
end
|
233
245
|
end
|
234
246
|
```
|
235
|
-
This grants access to `index` action for users with `accountant` or `admin` role, and access to `
|
247
|
+
This grants access to `index` action for users with `accountant` or `admin` role, and access to `destroy` action for `admin` users only.
|
248
|
+
|
249
|
+
Please note that Rabarber does not provide any built-in data scoping mechanism as it is not a part of the authorization layer and is not necessarily role specific or has anything to do with the current user. The business logic can vary drastically depending on the application, so you're encouraged to limit the data visibility yourself, for example, in the same way as in the example above, where `accountant` role can only see paid invoices.
|
236
250
|
|
237
251
|
You can also define controller-wide rules (without `action` argument):
|
238
252
|
|
@@ -242,18 +256,18 @@ class Crm::BaseController < ApplicationController
|
|
242
256
|
|
243
257
|
grant_access action: :dashboard, roles: :marketer
|
244
258
|
def dashboard
|
245
|
-
...
|
259
|
+
# ...
|
246
260
|
end
|
247
261
|
end
|
248
262
|
|
249
263
|
class Crm::InvoicesController < Crm::BaseController
|
250
264
|
grant_access roles: :accountant
|
251
265
|
def index
|
252
|
-
...
|
266
|
+
# ...
|
253
267
|
end
|
254
268
|
|
255
269
|
def delete
|
256
|
-
...
|
270
|
+
# ...
|
257
271
|
end
|
258
272
|
end
|
259
273
|
```
|
@@ -264,35 +278,53 @@ Roles can also be omitted:
|
|
264
278
|
```rb
|
265
279
|
class OrdersController < ApplicationController
|
266
280
|
grant_access
|
267
|
-
...
|
281
|
+
# ...
|
268
282
|
end
|
269
283
|
|
270
284
|
class InvoicesController < ApplicationController
|
271
285
|
grant_access action: :index
|
272
286
|
def index
|
273
|
-
...
|
287
|
+
# ...
|
274
288
|
end
|
275
289
|
end
|
276
290
|
```
|
277
291
|
|
278
292
|
This allows everyone to access `OrdersController` and its children and also `index` action in `InvoicesController`. This extends to scenarios where there is no user present, i.e. when the method responsible for returning the currently authenticated user in your application returns `nil`.
|
279
293
|
|
280
|
-
|
294
|
+
If the user is not authenticated (the method responsible for returning the currently authenticated user in your application returns `nil`), Rabarber will handle this situation as if the user has no roles.
|
281
295
|
|
282
|
-
If you've set `must_have_roles` setting to `true`, then
|
296
|
+
If you've set `must_have_roles` setting to `true`, then only the users with at least one role can gain access. This setting can be useful if your requirements are such that users without roles (or unauthenticated users) are not allowed to access anything.
|
297
|
+
|
298
|
+
Also keep in mind that rules defined in child classes don't override parent rules but rather add to them:
|
299
|
+
```rb
|
300
|
+
class Crm::BaseController < ApplicationController
|
301
|
+
grant_access roles: :admin
|
302
|
+
# ...
|
303
|
+
end
|
304
|
+
|
305
|
+
class Crm::InvoicesController < Crm::BaseController
|
306
|
+
grant_access roles: :accountant
|
307
|
+
# ...
|
308
|
+
end
|
309
|
+
```
|
310
|
+
This means that `Crm::InvoicesController` is still accessible to `admin` but is also accessible to `accountant`.
|
311
|
+
|
312
|
+
## Dynamic Authorization Rules
|
283
313
|
|
284
314
|
For more complex cases, Rabarber provides dynamic rules:
|
285
315
|
|
286
316
|
```rb
|
287
|
-
class OrdersController < ApplicationController
|
288
|
-
grant_access if: :
|
289
|
-
|
290
|
-
|
317
|
+
class Crm::OrdersController < ApplicationController
|
318
|
+
grant_access roles: :manager, if: :company_manager?, unless: :fired?
|
319
|
+
|
320
|
+
def index
|
321
|
+
# ...
|
322
|
+
end
|
291
323
|
|
292
324
|
private
|
293
325
|
|
294
|
-
def
|
295
|
-
|
326
|
+
def company_manager?
|
327
|
+
Company.find(params[:company_id]).manager == current_user
|
296
328
|
end
|
297
329
|
|
298
330
|
def fired?
|
@@ -300,33 +332,65 @@ class OrdersController < ApplicationController
|
|
300
332
|
end
|
301
333
|
end
|
302
334
|
|
303
|
-
class InvoicesController < ApplicationController
|
304
|
-
grant_access
|
335
|
+
class Crm::InvoicesController < ApplicationController
|
336
|
+
grant_access roles: :senior_accountant
|
337
|
+
|
338
|
+
grant_access action: :index, roles: [:secretary, :accountant], if: -> { InvoicesPolicy.new(current_user).can_access?(:index) }
|
305
339
|
def index
|
306
|
-
|
340
|
+
@invoices = Invoice.all
|
341
|
+
@invoices = @invoices.where("total < 10000") if current_user.has_role?(:accountant)
|
342
|
+
@invoices = @invoices.unpaid if current_user.has_role?(:secretary)
|
343
|
+
# ...
|
307
344
|
end
|
308
345
|
|
309
|
-
grant_access action: :show, roles: :
|
346
|
+
grant_access action: :show, roles: :accountant, unless: -> { Invoice.find(params[:id]).total > 10_000 }
|
310
347
|
def show
|
311
|
-
...
|
348
|
+
# ...
|
312
349
|
end
|
313
350
|
end
|
314
351
|
```
|
315
|
-
You can pass a dynamic rule as `if` or `unless` argument. It can be a symbol, in which case the method with
|
352
|
+
You can pass a dynamic rule as `if` or `unless` argument. It can be a symbol, in which case the method with that name will be called. Alternatively, it can be a proc, which will be executed within the context of the controller's instance.
|
316
353
|
|
317
|
-
|
354
|
+
You can use only dynamic rules without specifying roles if that suits your needs:
|
318
355
|
```rb
|
319
|
-
class
|
320
|
-
grant_access
|
321
|
-
|
356
|
+
class InvoicesController < ApplicationController
|
357
|
+
grant_access action: :index, if: -> { current_user.company == Company.find(params[:company_id]) }
|
358
|
+
def index
|
359
|
+
# ...
|
360
|
+
end
|
361
|
+
end
|
362
|
+
```
|
363
|
+
This basically allows you to use Rabarber as a policy-based authorization library by calling your own custom policy within a dynamic rule:
|
364
|
+
```rb
|
365
|
+
class InvoicesController < ApplicationController
|
366
|
+
grant_access action: :index, if: -> { InvoicesPolicy.new(current_user).can_access?(:index) }
|
367
|
+
def index
|
368
|
+
# ...
|
369
|
+
end
|
322
370
|
end
|
371
|
+
```
|
323
372
|
|
324
|
-
|
325
|
-
|
326
|
-
|
373
|
+
## When Unauthorized
|
374
|
+
|
375
|
+
By default, in the event of an unauthorized attempt, Rabarber redirects the user back if the request format is HTML (with fallback to the root path), and returns a 401 (Unauthorized) status code otherwise.
|
376
|
+
|
377
|
+
This behavior can be customized by overriding private `when_unauthorized` method:
|
378
|
+
|
379
|
+
```rb
|
380
|
+
class ApplicationController < ActionController::Base
|
381
|
+
include Rabarber::Authorization
|
382
|
+
|
383
|
+
# ...
|
384
|
+
|
385
|
+
private
|
386
|
+
|
387
|
+
def when_unauthorized
|
388
|
+
head :not_found # pretend the page doesn't exist
|
389
|
+
end
|
327
390
|
end
|
328
391
|
```
|
329
|
-
|
392
|
+
|
393
|
+
The method can be overridden in different controllers, providing flexibility in handling unauthorized access attempts.
|
330
394
|
|
331
395
|
## View Helpers
|
332
396
|
|
@@ -335,7 +399,7 @@ Rabarber also provides a couple of helpers that can be used in views: `visible_t
|
|
335
399
|
```rb
|
336
400
|
module ApplicationHelper
|
337
401
|
include Rabarber::Helpers
|
338
|
-
...
|
402
|
+
# ...
|
339
403
|
end
|
340
404
|
```
|
341
405
|
|
@@ -367,7 +431,7 @@ The logs are written to the file `log/rabarber_audit.log` unless the `audit_trai
|
|
367
431
|
|
368
432
|
Facing a problem or want to suggest an enhancement?
|
369
433
|
|
370
|
-
- **Open a Discussion**: If you have a question, experience difficulties using the gem, or have
|
434
|
+
- **Open a Discussion**: If you have a question, experience difficulties using the gem, or have a suggestion for improvements, feel free to use the Discussions section.
|
371
435
|
|
372
436
|
Encountered a bug?
|
373
437
|
|
@@ -376,12 +440,12 @@ Encountered a bug?
|
|
376
440
|
|
377
441
|
## Contributing
|
378
442
|
|
379
|
-
Before
|
380
|
-
|
381
|
-
## License
|
382
|
-
|
383
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
443
|
+
Before creating an issue or a pull request, please read the [contributing guidelines](https://github.com/enjaku4/rabarber/blob/main/CONTRIBUTING.md).
|
384
444
|
|
385
445
|
## Code of Conduct
|
386
446
|
|
387
447
|
Everyone interacting in the Rabarber project is expected to follow the [code of conduct](https://github.com/enjaku4/rabarber/blob/main/CODE_OF_CONDUCT.md).
|
448
|
+
|
449
|
+
## License
|
450
|
+
|
451
|
+
The gem is available as open source under the terms of the [MIT License](https://github.com/enjaku4/rabarber/blob/main/LICENSE.txt).
|
@@ -10,6 +10,8 @@ module Rabarber
|
|
10
10
|
|
11
11
|
argument :table_name, type: :string, required: true
|
12
12
|
|
13
|
+
class_option :uuid, type: :boolean, default: false, desc: "Use UUIDs as primary keys"
|
14
|
+
|
13
15
|
def create_migrations
|
14
16
|
migration_template "create_rabarber_roles.rb.erb", "db/migrate/create_rabarber_roles.rb"
|
15
17
|
end
|
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
class CreateRabarberRoles < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version.to_s %>]
|
4
4
|
def change
|
5
|
-
create_table :rabarber_roles do |t|
|
5
|
+
create_table :rabarber_roles<%= ", id: :uuid" if options[:uuid] %> do |t|
|
6
6
|
t.string :name, null: false, index: { unique: true }
|
7
7
|
t.timestamps
|
8
8
|
end
|
9
9
|
|
10
10
|
create_table :rabarber_roles_roleables, id: false do |t|
|
11
|
-
t.belongs_to :role, null: false, index: true, foreign_key: { to_table: :rabarber_roles }
|
12
|
-
t.belongs_to :roleable, null: false, index: true, foreign_key: { to_table: <%= table_name.to_sym.inspect %> }
|
11
|
+
t.belongs_to :role, null: false, index: true, foreign_key: { to_table: :rabarber_roles }<%= ", type: :uuid" if options[:uuid] %>
|
12
|
+
t.belongs_to :roleable, null: false, index: true, foreign_key: { to_table: <%= table_name.to_sym.inspect %> }<%= ", type: :uuid" if options[:uuid] %>
|
13
13
|
end
|
14
14
|
|
15
15
|
add_index :rabarber_roles_roleables, [:role_id, :roleable_id], unique: true
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../logger"
|
4
|
+
|
5
|
+
module Rabarber
|
6
|
+
module Audit
|
7
|
+
module Events
|
8
|
+
class Base
|
9
|
+
attr_reader :roleable, :specifics
|
10
|
+
|
11
|
+
def self.trigger(roleable, specifics)
|
12
|
+
new(roleable, specifics).send(:log)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def initialize(roleable, specifics)
|
18
|
+
raise ArgumentError, "Roleable is required for #{self.class} event" if roleable.nil? && !nil_roleable_allowed?
|
19
|
+
|
20
|
+
@roleable = roleable
|
21
|
+
@specifics = specifics
|
22
|
+
end
|
23
|
+
|
24
|
+
def nil_roleable_allowed?
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
def log
|
29
|
+
Rabarber::Audit::Logger.log(log_level, message)
|
30
|
+
end
|
31
|
+
|
32
|
+
def log_level
|
33
|
+
raise NotImplementedError
|
34
|
+
end
|
35
|
+
|
36
|
+
def message
|
37
|
+
raise NotImplementedError
|
38
|
+
end
|
39
|
+
|
40
|
+
def identity
|
41
|
+
roleable_identity(with_roles: identity_with_roles?)
|
42
|
+
end
|
43
|
+
|
44
|
+
def identity_with_roles?
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
def roleable_identity(with_roles:)
|
49
|
+
if roleable
|
50
|
+
model_name = roleable.model_name.human
|
51
|
+
primary_key = roleable.class.primary_key
|
52
|
+
roleable_id = roleable.public_send(primary_key)
|
53
|
+
|
54
|
+
roles = with_roles ? ", roles: #{roleable.roles}" : ""
|
55
|
+
|
56
|
+
"#{model_name} with #{primary_key}: '#{roleable_id}'#{roles}"
|
57
|
+
else
|
58
|
+
"Unauthenticated #{Rabarber::HasRoles.roleable_class.model_name.human.downcase}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|