rabarber 1.4.1 → 2.1.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 +28 -0
- data/README.md +133 -67
- 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 +13 -11
- data/lib/rabarber/core/access.rb +3 -1
- 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: 2cf7603eb6340263f349ffd661101f6428d210982b2a4dfb0d121f19814db3b7
|
4
|
+
data.tar.gz: e31584352b9ea5565bfbc4e9a9f53084c6b9123643eb6919557ef52b9dfb3efb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 757631f95940e95d1640d16101b1fafe049808178ade0d0d97aac3163d0c31c581043bbeeab5b94da9ba4ed28e9edf890aba9adc845e80e9ab1b39ae22c6d66b
|
7
|
+
data.tar.gz: 0eb8500deced87bea565d146de0dcd236ffeacac86e025bd6c5cd484fad94cb8594158575c88e31c69fec1f9d0a83f5328029ee261a343603505f847cad5e989
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,31 @@
|
|
1
|
+
## v2.1.0
|
2
|
+
|
3
|
+
### Features:
|
4
|
+
|
5
|
+
- Added `Rabarber::Authorization.skip_authorization` method to skip authorization checks
|
6
|
+
|
7
|
+
## v2.0.0
|
8
|
+
|
9
|
+
### Breaking:
|
10
|
+
|
11
|
+
- Removed `when_actions_missing` and `when_roles_missing` configuration options
|
12
|
+
- Replaced `when_unauthorized` configuration option with an overridable controller method
|
13
|
+
- Renamed `Rabarber::Role.assignees_for` method to `Rabarber::Role.assignees`
|
14
|
+
|
15
|
+
To upgrade to v2.0.0, please refer to the [migration guide](https://github.com/enjaku4/rabarber/discussions/52).
|
16
|
+
|
17
|
+
### Features:
|
18
|
+
|
19
|
+
- Added support for UUID primary keys
|
20
|
+
|
21
|
+
### Bugs:
|
22
|
+
|
23
|
+
- Fixed the issue where an error would occur when using view helpers if the user was not authenticated
|
24
|
+
|
25
|
+
### Misc:
|
26
|
+
|
27
|
+
- Significant refactoring and code improvements
|
28
|
+
|
1
29
|
## v1.4.1
|
2
30
|
|
3
31
|
- Fix an issue where an error could be raised when using controller-wide dynamic rules
|
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,66 @@ 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
|
+
- [Skip Authorization](#skip-authorization)
|
44
|
+
- [View Helpers](#view-helpers)
|
45
|
+
- [Audit Trail](#audit-trail)
|
46
|
+
|
47
|
+
**Community Resources:**
|
48
|
+
- [Problems?](#problems)
|
49
|
+
- [Contributing](#contributing)
|
50
|
+
- [Code of Conduct](#code-of-conduct)
|
51
|
+
|
52
|
+
**Legal:**
|
53
|
+
- [License](#license)
|
54
|
+
|
34
55
|
## Installation
|
35
56
|
|
36
57
|
Add the Rabarber gem to your Gemfile:
|
37
58
|
|
38
|
-
```
|
59
|
+
```rb
|
39
60
|
gem "rabarber"
|
40
61
|
```
|
41
62
|
|
42
63
|
Install the gem:
|
43
64
|
|
44
|
-
```
|
65
|
+
```shell
|
45
66
|
bundle install
|
46
67
|
```
|
47
68
|
|
48
69
|
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
70
|
|
50
|
-
```
|
71
|
+
```shell
|
51
72
|
rails g rabarber:roles users
|
52
73
|
```
|
53
74
|
|
54
|
-
|
75
|
+
Rabarber supports UUIDs as primary keys. If your application uses UUIDs, add `--uuid` option to the generator:
|
55
76
|
|
77
|
+
```shell
|
78
|
+
rails g rabarber:roles users --uuid
|
56
79
|
```
|
80
|
+
|
81
|
+
Finally, run the migration to apply the changes to the database:
|
82
|
+
|
83
|
+
```shell
|
57
84
|
rails db:migrate
|
58
85
|
```
|
59
86
|
|
@@ -67,28 +94,13 @@ Rabarber.configure do |config|
|
|
67
94
|
config.cache_enabled = true
|
68
95
|
config.current_user_method = :current_user
|
69
96
|
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
97
|
end
|
78
98
|
```
|
79
99
|
|
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
|
-
- `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._
|
100
|
+
- `audit_trail_enabled` determines whether the audit trail functionality is enabled. The audit trail is enabled by default.
|
101
|
+
- `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.
|
102
|
+
- `current_user_method` represents the method that returns the currently authenticated user. The default value is `:current_user`.
|
103
|
+
- `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).
|
92
104
|
|
93
105
|
## Roles
|
94
106
|
|
@@ -97,7 +109,7 @@ Include `Rabarber::HasRoles` module in your model representing users in your app
|
|
97
109
|
```rb
|
98
110
|
class User < ApplicationRecord
|
99
111
|
include Rabarber::HasRoles
|
100
|
-
...
|
112
|
+
# ...
|
101
113
|
end
|
102
114
|
```
|
103
115
|
|
@@ -123,7 +135,7 @@ To revoke roles, use:
|
|
123
135
|
```rb
|
124
136
|
user.revoke_roles(:accountant, :marketer)
|
125
137
|
```
|
126
|
-
If
|
138
|
+
If the user doesn't have the role you want to revoke, it will be ignored.
|
127
139
|
|
128
140
|
The method returns an array of roles assigned to the user.
|
129
141
|
|
@@ -139,7 +151,7 @@ It returns `true` if the user has at least one role and `false` otherwise.
|
|
139
151
|
|
140
152
|
**`#roles`**
|
141
153
|
|
142
|
-
To
|
154
|
+
To get all the roles assigned to the user, use:
|
143
155
|
|
144
156
|
```rb
|
145
157
|
user.roles
|
@@ -149,7 +161,7 @@ user.roles
|
|
149
161
|
|
150
162
|
To manipulate roles directly, you can use `Rabarber::Role` methods:
|
151
163
|
|
152
|
-
**`.add(
|
164
|
+
**`.add(role_name)`**
|
153
165
|
|
154
166
|
To add a new role, use:
|
155
167
|
|
@@ -166,14 +178,14 @@ To rename a role, use:
|
|
166
178
|
```rb
|
167
179
|
Rabarber::Role.rename(:admin, :administrator)
|
168
180
|
```
|
169
|
-
The first argument is the old name, and the second argument is the new name. This will rename the role and return `true`. If
|
181
|
+
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`.
|
170
182
|
|
171
183
|
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:
|
172
184
|
```rb
|
173
185
|
Rabarber::Role.rename(:admin, :administrator, force: true)
|
174
186
|
```
|
175
187
|
|
176
|
-
**`.remove(
|
188
|
+
**`.remove(role_name, force: false)`**
|
177
189
|
|
178
190
|
To remove a role, use:
|
179
191
|
|
@@ -196,12 +208,12 @@ If you need to list all the role names available in your application, use:
|
|
196
208
|
Rabarber::Role.names
|
197
209
|
```
|
198
210
|
|
199
|
-
**`.
|
211
|
+
**`.assignees(role_name)`**
|
200
212
|
|
201
213
|
To get all the users to whom the role is assigned, use:
|
202
214
|
|
203
215
|
```rb
|
204
|
-
Rabarber::Role.
|
216
|
+
Rabarber::Role.assignees(:admin)
|
205
217
|
```
|
206
218
|
|
207
219
|
## Authorization Rules
|
@@ -211,7 +223,7 @@ Include `Rabarber::Authorization` module into the controller that needs authoriz
|
|
211
223
|
```rb
|
212
224
|
class ApplicationController < ActionController::Base
|
213
225
|
include Rabarber::Authorization
|
214
|
-
...
|
226
|
+
# ...
|
215
227
|
end
|
216
228
|
```
|
217
229
|
This adds `.grant_access(action: nil, roles: nil, if: nil, unless: nil)` method which allows you to define the authorization rules.
|
@@ -219,17 +231,17 @@ This adds `.grant_access(action: nil, roles: nil, if: nil, unless: nil)` method
|
|
219
231
|
The most basic usage of the method is as follows:
|
220
232
|
|
221
233
|
```rb
|
222
|
-
class InvoicesController < ApplicationController
|
234
|
+
class Crm::InvoicesController < ApplicationController
|
223
235
|
grant_access action: :index, roles: [:accountant, :admin]
|
224
236
|
def index
|
225
237
|
@invoices = Invoice.all
|
226
238
|
@invoices = @invoices.paid if current_user.has_role?(:accountant)
|
227
|
-
...
|
239
|
+
# ...
|
228
240
|
end
|
229
241
|
|
230
242
|
grant_access action: :destroy, roles: :admin
|
231
243
|
def destroy
|
232
|
-
...
|
244
|
+
# ...
|
233
245
|
end
|
234
246
|
end
|
235
247
|
```
|
@@ -243,18 +255,18 @@ class Crm::BaseController < ApplicationController
|
|
243
255
|
|
244
256
|
grant_access action: :dashboard, roles: :marketer
|
245
257
|
def dashboard
|
246
|
-
...
|
258
|
+
# ...
|
247
259
|
end
|
248
260
|
end
|
249
261
|
|
250
262
|
class Crm::InvoicesController < Crm::BaseController
|
251
263
|
grant_access roles: :accountant
|
252
264
|
def index
|
253
|
-
...
|
265
|
+
# ...
|
254
266
|
end
|
255
267
|
|
256
268
|
def delete
|
257
|
-
...
|
269
|
+
# ...
|
258
270
|
end
|
259
271
|
end
|
260
272
|
```
|
@@ -265,31 +277,45 @@ Roles can also be omitted:
|
|
265
277
|
```rb
|
266
278
|
class OrdersController < ApplicationController
|
267
279
|
grant_access
|
268
|
-
...
|
280
|
+
# ...
|
269
281
|
end
|
270
282
|
|
271
283
|
class InvoicesController < ApplicationController
|
272
284
|
grant_access action: :index
|
273
285
|
def index
|
274
|
-
...
|
286
|
+
# ...
|
275
287
|
end
|
276
288
|
end
|
277
289
|
```
|
278
290
|
|
279
|
-
This allows everyone to access `OrdersController` and its children and also `index` action in `InvoicesController`.
|
291
|
+
This allows everyone to access `OrdersController` and its children and also `index` action in `InvoicesController`.
|
280
292
|
|
281
|
-
|
293
|
+
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 are not allowed to access anything.
|
282
294
|
|
283
|
-
|
295
|
+
Also keep in mind that rules defined in child classes don't override parent rules but rather add to them:
|
296
|
+
```rb
|
297
|
+
class Crm::BaseController < ApplicationController
|
298
|
+
grant_access roles: :admin
|
299
|
+
# ...
|
300
|
+
end
|
301
|
+
|
302
|
+
class Crm::InvoicesController < Crm::BaseController
|
303
|
+
grant_access roles: :accountant
|
304
|
+
# ...
|
305
|
+
end
|
306
|
+
```
|
307
|
+
This means that `Crm::InvoicesController` is still accessible to `admin` but is also accessible to `accountant`.
|
308
|
+
|
309
|
+
## Dynamic Authorization Rules
|
284
310
|
|
285
311
|
For more complex cases, Rabarber provides dynamic rules:
|
286
312
|
|
287
313
|
```rb
|
288
|
-
class OrdersController < ApplicationController
|
314
|
+
class Crm::OrdersController < ApplicationController
|
289
315
|
grant_access roles: :manager, if: :company_manager?, unless: :fired?
|
290
316
|
|
291
317
|
def index
|
292
|
-
...
|
318
|
+
# ...
|
293
319
|
end
|
294
320
|
|
295
321
|
private
|
@@ -303,7 +329,7 @@ class OrdersController < ApplicationController
|
|
303
329
|
end
|
304
330
|
end
|
305
331
|
|
306
|
-
class InvoicesController < ApplicationController
|
332
|
+
class Crm::InvoicesController < ApplicationController
|
307
333
|
grant_access roles: :senior_accountant
|
308
334
|
|
309
335
|
grant_access action: :index, roles: [:secretary, :accountant], if: -> { InvoicesPolicy.new(current_user).can_access?(:index) }
|
@@ -311,30 +337,70 @@ class InvoicesController < ApplicationController
|
|
311
337
|
@invoices = Invoice.all
|
312
338
|
@invoices = @invoices.where("total < 10000") if current_user.has_role?(:accountant)
|
313
339
|
@invoices = @invoices.unpaid if current_user.has_role?(:secretary)
|
314
|
-
...
|
340
|
+
# ...
|
315
341
|
end
|
316
342
|
|
317
343
|
grant_access action: :show, roles: :accountant, unless: -> { Invoice.find(params[:id]).total > 10_000 }
|
318
344
|
def show
|
319
|
-
...
|
345
|
+
# ...
|
320
346
|
end
|
321
347
|
end
|
322
348
|
```
|
323
|
-
You can pass a dynamic rule as `if` or `unless` argument. It can be a symbol, in which case the method with
|
349
|
+
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, or alternatively it can be a proc that will be executed within the context of the controller instance at request time.
|
324
350
|
|
325
|
-
|
351
|
+
You can use only dynamic rules without specifying roles if that suits your needs:
|
326
352
|
```rb
|
327
|
-
class
|
328
|
-
grant_access
|
329
|
-
|
353
|
+
class InvoicesController < ApplicationController
|
354
|
+
grant_access action: :index, if: -> { current_user.company == Company.find(params[:company_id]) }
|
355
|
+
def index
|
356
|
+
# ...
|
357
|
+
end
|
358
|
+
end
|
359
|
+
```
|
360
|
+
This basically allows you to use Rabarber as a policy-based authorization library by calling your own custom policy within a dynamic rule:
|
361
|
+
```rb
|
362
|
+
class InvoicesController < ApplicationController
|
363
|
+
grant_access action: :index, if: -> { InvoicesPolicy.new(current_user).can_access?(:index) }
|
364
|
+
def index
|
365
|
+
# ...
|
366
|
+
end
|
330
367
|
end
|
368
|
+
```
|
331
369
|
|
332
|
-
|
333
|
-
|
334
|
-
|
370
|
+
## When Unauthorized
|
371
|
+
|
372
|
+
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.
|
373
|
+
|
374
|
+
This behavior can be customized by overriding private `when_unauthorized` method:
|
375
|
+
|
376
|
+
```rb
|
377
|
+
class ApplicationController < ActionController::Base
|
378
|
+
include Rabarber::Authorization
|
379
|
+
|
380
|
+
# ...
|
381
|
+
|
382
|
+
private
|
383
|
+
|
384
|
+
def when_unauthorized
|
385
|
+
head :not_found # pretend the page doesn't exist
|
386
|
+
end
|
335
387
|
end
|
336
388
|
```
|
337
|
-
|
389
|
+
|
390
|
+
The method can be overridden in different controllers, providing flexibility in handling unauthorized access attempts.
|
391
|
+
|
392
|
+
## Skip Authorization
|
393
|
+
|
394
|
+
To skip authorization, use `.skip_authorization(options = {})` method:
|
395
|
+
|
396
|
+
```rb
|
397
|
+
class TicketsController < ApplicationController
|
398
|
+
skip_authorization only: :index
|
399
|
+
# ...
|
400
|
+
end
|
401
|
+
```
|
402
|
+
|
403
|
+
This method accepts the same options as `skip_before_action` method in Rails.
|
338
404
|
|
339
405
|
## View Helpers
|
340
406
|
|
@@ -343,7 +409,7 @@ Rabarber also provides a couple of helpers that can be used in views: `visible_t
|
|
343
409
|
```rb
|
344
410
|
module ApplicationHelper
|
345
411
|
include Rabarber::Helpers
|
346
|
-
...
|
412
|
+
# ...
|
347
413
|
end
|
348
414
|
```
|
349
415
|
|
@@ -375,7 +441,7 @@ The logs are written to the file `log/rabarber_audit.log` unless the `audit_trai
|
|
375
441
|
|
376
442
|
Facing a problem or want to suggest an enhancement?
|
377
443
|
|
378
|
-
- **Open a Discussion**: If you have a question, experience difficulties using the gem, or have
|
444
|
+
- **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.
|
379
445
|
|
380
446
|
Encountered a bug?
|
381
447
|
|
@@ -384,12 +450,12 @@ Encountered a bug?
|
|
384
450
|
|
385
451
|
## Contributing
|
386
452
|
|
387
|
-
Before
|
388
|
-
|
389
|
-
## License
|
390
|
-
|
391
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
453
|
+
Before creating an issue or a pull request, please read the [contributing guidelines](https://github.com/enjaku4/rabarber/blob/main/CONTRIBUTING.md).
|
392
454
|
|
393
455
|
## Code of Conduct
|
394
456
|
|
395
457
|
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).
|
458
|
+
|
459
|
+
## License
|
460
|
+
|
461
|
+
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
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Audit
|
5
|
+
module Events
|
6
|
+
class RolesAssigned < Base
|
7
|
+
private
|
8
|
+
|
9
|
+
def nil_roleable_allowed?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def log_level
|
14
|
+
:info
|
15
|
+
end
|
16
|
+
|
17
|
+
def message
|
18
|
+
"[Role Assignment] #{identity} has been assigned the following roles: #{roles_to_assign}, current roles: #{current_roles}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def identity_with_roles?
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def roles_to_assign
|
26
|
+
specifics.fetch(:roles_to_assign)
|
27
|
+
end
|
28
|
+
|
29
|
+
def current_roles
|
30
|
+
specifics.fetch(:current_roles)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Audit
|
5
|
+
module Events
|
6
|
+
class RolesRevoked < Base
|
7
|
+
private
|
8
|
+
|
9
|
+
def nil_roleable_allowed?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def log_level
|
14
|
+
:info
|
15
|
+
end
|
16
|
+
|
17
|
+
def message
|
18
|
+
"[Role Revocation] #{identity} has been revoked from the following roles: #{roles_to_revoke}, current roles: #{current_roles}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def identity_with_roles?
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def roles_to_revoke
|
26
|
+
specifics.fetch(:roles_to_revoke)
|
27
|
+
end
|
28
|
+
|
29
|
+
def current_roles
|
30
|
+
specifics.fetch(:current_roles)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Audit
|
5
|
+
module Events
|
6
|
+
class UnauthorizedAttempt < Base
|
7
|
+
private
|
8
|
+
|
9
|
+
def nil_roleable_allowed?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def log_level
|
14
|
+
:warn
|
15
|
+
end
|
16
|
+
|
17
|
+
def message
|
18
|
+
"[Unauthorized Attempt] #{identity} attempted to access '#{path}'"
|
19
|
+
end
|
20
|
+
|
21
|
+
def identity_with_roles?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def path
|
26
|
+
specifics.fetch(:path)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
|
5
|
+
module Rabarber
|
6
|
+
module Audit
|
7
|
+
class Logger
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
attr_reader :logger
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@logger = ::Logger.new(Rails.root.join("log/rabarber_audit.log"))
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.log(log_level, message)
|
17
|
+
return unless Rabarber::Configuration.instance.audit_trail_enabled
|
18
|
+
|
19
|
+
instance.logger.public_send(log_level, message)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|