rabarber 4.1.4 → 5.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/CHANGELOG.md +43 -3
- data/README.md +200 -385
- data/lib/rabarber/configuration.rb +14 -11
- data/lib/rabarber/controllers/concerns/authorization.rb +11 -11
- data/lib/rabarber/core/integrity_checker.rb +44 -0
- data/lib/rabarber/core/permissions.rb +5 -0
- data/lib/rabarber/core/roleable.rb +10 -3
- data/lib/rabarber/core/rule.rb +6 -4
- data/lib/rabarber/helpers/helpers.rb +4 -4
- data/lib/rabarber/helpers/migration_helpers.rb +29 -0
- data/lib/rabarber/input/ar_model.rb +23 -0
- data/lib/rabarber/models/concerns/has_roles.rb +11 -24
- data/lib/rabarber/models/role.rb +20 -33
- data/lib/rabarber/railtie.rb +26 -1
- data/lib/rabarber/version.rb +1 -1
- data/lib/rabarber.rb +4 -6
- data/rabarber.gemspec +3 -4
- metadata +13 -23
- data/lib/rabarber/audit/events/base.rb +0 -49
- data/lib/rabarber/audit/events/roles_assigned.rb +0 -31
- data/lib/rabarber/audit/events/roles_revoked.rb +0 -31
- data/lib/rabarber/audit/events/unauthorized_attempt.rb +0 -27
- data/lib/rabarber/audit/logger.rb +0 -23
- data/lib/rabarber/core/null_roleable.rb +0 -23
- data/lib/rabarber/core/permissions_integrity_checker.rb +0 -36
data/README.md
CHANGED
@@ -1,61 +1,40 @@
|
|
1
|
-
# Rabarber:
|
1
|
+
# Rabarber: Role-Based Authorization for Rails
|
2
2
|
|
3
3
|
[](http://badge.fury.io/rb/rabarber)
|
4
|
-
[](https://github.com/brownboxdev/rabarber/actions/workflows/ci.yml)
|
5
5
|
|
6
|
-
Rabarber is a role-based authorization library for Ruby on Rails.
|
6
|
+
Rabarber is a role-based authorization library for Ruby on Rails that focuses on controller-level access control. Rather than answering domain questions like "can this user create a post?", Rabarber answers "can this user access the create post endpoint?", providing a clean separation between authorization and business logic.
|
7
7
|
|
8
|
-
|
8
|
+
**Key Features:**
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
And this is how your controller might look with Rabarber:
|
17
|
-
|
18
|
-
```rb
|
19
|
-
class TicketsController < ApplicationController
|
20
|
-
grant_access roles: :admin
|
21
|
-
|
22
|
-
grant_access action: :index, roles: :manager
|
23
|
-
def index
|
24
|
-
# ...
|
25
|
-
end
|
26
|
-
|
27
|
-
def delete
|
28
|
-
# ...
|
29
|
-
end
|
30
|
-
end
|
31
|
-
```
|
32
|
-
This means that `admin` users can access everything in `TicketsController`, while `manager` role can access only `index` action.
|
10
|
+
- Role-based authorization
|
11
|
+
- Controller-level access control
|
12
|
+
- Multi-tenancy support through contextual roles
|
13
|
+
- Dynamic authorization with conditional logic
|
14
|
+
- View helpers for role-based content rendering
|
33
15
|
|
34
16
|
## Table of Contents
|
35
17
|
|
36
|
-
**Gem
|
18
|
+
**Gem Usage:**
|
37
19
|
- [Installation](#installation)
|
38
20
|
- [Configuration](#configuration)
|
39
|
-
- [
|
40
|
-
- [
|
41
|
-
- [
|
42
|
-
- [
|
43
|
-
- [
|
44
|
-
- [Skip Authorization](#skip-authorization)
|
21
|
+
- [Role Management](#role-management)
|
22
|
+
- [User Role Methods](#user-role-methods)
|
23
|
+
- [Controller Authorization](#controller-authorization)
|
24
|
+
- [Dynamic Rules](#dynamic-rules)
|
25
|
+
- [Multi-tenancy / Context](#multi-tenancy--context)
|
45
26
|
- [View Helpers](#view-helpers)
|
46
|
-
|
27
|
+
|
47
28
|
|
48
29
|
**Community Resources:**
|
49
|
-
- [Problems?](#problems)
|
50
30
|
- [Contributing](#contributing)
|
51
|
-
- [Code of Conduct](#code-of-conduct)
|
52
|
-
|
53
|
-
**Legal:**
|
54
31
|
- [License](#license)
|
32
|
+
- [Code of Conduct](#code-of-conduct)
|
33
|
+
- [Old Versions](#old-versions)
|
55
34
|
|
56
35
|
## Installation
|
57
36
|
|
58
|
-
Add
|
37
|
+
Add Rabarber to your Gemfile:
|
59
38
|
|
60
39
|
```rb
|
61
40
|
gem "rabarber"
|
@@ -67,19 +46,17 @@ Install the gem:
|
|
67
46
|
bundle install
|
68
47
|
```
|
69
48
|
|
70
|
-
|
49
|
+
Generate the migration for role storage (replace `users` with your user table name if different):
|
71
50
|
|
72
51
|
```shell
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
Rabarber supports UUIDs as primary keys. If your application uses UUIDs, add `--uuid` option to the generator:
|
52
|
+
# For standard integer IDs
|
53
|
+
rails generate rabarber:roles users
|
77
54
|
|
78
|
-
|
79
|
-
rails
|
55
|
+
# For UUID primary keys
|
56
|
+
rails generate rabarber:roles users --uuid
|
80
57
|
```
|
81
58
|
|
82
|
-
|
59
|
+
Run the migration:
|
83
60
|
|
84
61
|
```shell
|
85
62
|
rails db:migrate
|
@@ -87,493 +64,331 @@ rails db:migrate
|
|
87
64
|
|
88
65
|
## Configuration
|
89
66
|
|
90
|
-
|
67
|
+
Configure Rabarber in an initializer if customization is needed:
|
91
68
|
|
92
69
|
```rb
|
93
70
|
Rabarber.configure do |config|
|
94
|
-
config.
|
95
|
-
config.
|
96
|
-
config.
|
97
|
-
config.must_have_roles = false
|
98
|
-
end
|
99
|
-
```
|
100
|
-
|
101
|
-
- `audit_trail_enabled` determines whether the audit trail functionality is enabled. The audit trail is enabled by default.
|
102
|
-
- `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.
|
103
|
-
- `current_user_method` represents the method that returns the currently authenticated user. The default value is `:current_user`.
|
104
|
-
- `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).
|
105
|
-
|
106
|
-
## Roles
|
107
|
-
|
108
|
-
Include `Rabarber::HasRoles` module in your model representing users in your application:
|
109
|
-
|
110
|
-
```rb
|
111
|
-
class User < ApplicationRecord
|
112
|
-
include Rabarber::HasRoles
|
113
|
-
# ...
|
71
|
+
config.cache_enabled = true # Enable role caching (default: true)
|
72
|
+
config.current_user_method = :current_user # Method to access current user (default: :current_user)
|
73
|
+
config.user_model_name = "User" # User model name (default: "User")
|
114
74
|
end
|
115
75
|
```
|
116
76
|
|
117
|
-
|
77
|
+
To clear the role cache manually:
|
118
78
|
|
119
|
-
**`#assign_roles(*roles, context: nil, create_new: true)`**
|
120
|
-
|
121
|
-
To assign roles, use:
|
122
|
-
|
123
|
-
```rb
|
124
|
-
user.assign_roles(:accountant, :marketer)
|
125
|
-
```
|
126
|
-
By default, it will automatically create any roles that don't exist. If you want to assign only existing roles and prevent the creation of new ones, use the method with `create_new: false` argument:
|
127
79
|
```rb
|
128
|
-
|
80
|
+
Rabarber::Cache.clear
|
129
81
|
```
|
130
|
-
The method returns an array of roles assigned to the user.
|
131
82
|
|
132
|
-
|
83
|
+
## Role Management
|
133
84
|
|
134
|
-
|
85
|
+
### Direct Role Operations
|
135
86
|
|
136
87
|
```rb
|
137
|
-
|
138
|
-
|
139
|
-
If the user doesn't have the role you want to revoke, it will be ignored.
|
88
|
+
# Create a new role
|
89
|
+
Rabarber::Role.add(:admin)
|
140
90
|
|
141
|
-
|
91
|
+
# Rename a role
|
92
|
+
Rabarber::Role.rename(:admin, :administrator)
|
93
|
+
Rabarber::Role.rename(:admin, :administrator, force: true) # Force if role is assigned to users
|
142
94
|
|
143
|
-
|
95
|
+
# Remove a role
|
96
|
+
Rabarber::Role.remove(:admin)
|
97
|
+
Rabarber::Role.remove(:admin, force: true) # Force if role is assigned to users
|
144
98
|
|
145
|
-
|
99
|
+
# List available roles
|
100
|
+
Rabarber::Role.names
|
101
|
+
Rabarber::Role.all_names # All roles grouped by context
|
146
102
|
|
147
|
-
|
148
|
-
|
103
|
+
# Get users assigned to a role
|
104
|
+
Rabarber::Role.assignees(:admin)
|
149
105
|
```
|
150
106
|
|
151
|
-
|
107
|
+
## User Role Methods
|
152
108
|
|
153
|
-
|
109
|
+
Your user model is automatically augmented with role management methods:
|
154
110
|
|
155
|
-
|
111
|
+
### Role Assignment
|
156
112
|
|
157
113
|
```rb
|
158
|
-
|
159
|
-
|
114
|
+
# Assign roles (creates roles if they don't exist)
|
115
|
+
user.assign_roles(:accountant, :manager)
|
160
116
|
|
161
|
-
|
117
|
+
# Assign only existing roles
|
118
|
+
user.assign_roles(:accountant, :manager, create_new: false)
|
162
119
|
|
163
|
-
|
120
|
+
# Revoke specific roles
|
121
|
+
user.revoke_roles(:accountant, :manager)
|
164
122
|
|
165
|
-
|
166
|
-
user.
|
123
|
+
# Revoke all roles
|
124
|
+
user.revoke_all_roles
|
167
125
|
```
|
168
126
|
|
169
|
-
|
170
|
-
|
171
|
-
To manipulate roles directly, you can use `Rabarber::Role` methods:
|
172
|
-
|
173
|
-
**`.add(role_name, context: nil)`**
|
174
|
-
|
175
|
-
To add a new role, use:
|
127
|
+
### Role Queries
|
176
128
|
|
177
129
|
```rb
|
178
|
-
|
179
|
-
|
130
|
+
# Check if user has any of the specified roles
|
131
|
+
user.has_role?(:accountant, :manager)
|
180
132
|
|
181
|
-
|
182
|
-
|
183
|
-
**`.rename(old_role_name, new_role_name, context: nil, force: false)`**
|
184
|
-
|
185
|
-
To rename a role, use:
|
133
|
+
# Get user's roles
|
134
|
+
user.roles
|
186
135
|
|
187
|
-
|
188
|
-
|
136
|
+
# Get all roles grouped by context
|
137
|
+
user.all_roles
|
189
138
|
```
|
190
|
-
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`.
|
191
139
|
|
192
|
-
|
193
|
-
```rb
|
194
|
-
Rabarber::Role.rename(:admin, :administrator, force: true)
|
195
|
-
```
|
140
|
+
## Controller Authorization
|
196
141
|
|
197
|
-
|
142
|
+
### Basic Setup
|
198
143
|
|
199
|
-
|
144
|
+
Include the authorization module and configure protection:
|
200
145
|
|
201
146
|
```rb
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
This will remove the role and return `true`. If the role doesn't exist, it will return `false`.
|
206
|
-
|
207
|
-
The method won't remove the role and will return `false` if it is assigned to any user. To force the removal, use the method with `force: true` argument:
|
208
|
-
```rb
|
209
|
-
Rabarber::Role.remove(:admin, force: true)
|
210
|
-
```
|
211
|
-
|
212
|
-
**`.names(context: nil)`**
|
147
|
+
class ApplicationController < ActionController::Base
|
148
|
+
include Rabarber::Authorization
|
213
149
|
|
214
|
-
|
150
|
+
with_authorization # Enable authorization for all actions
|
151
|
+
end
|
215
152
|
|
216
|
-
|
217
|
-
|
153
|
+
class InvoicesController < ApplicationController
|
154
|
+
with_authorization only: [:update, :destroy] # Selective authorization
|
155
|
+
end
|
218
156
|
```
|
219
157
|
|
220
|
-
|
221
|
-
|
222
|
-
If you need list all roles available in your application, grouped by context, use:
|
223
|
-
|
224
|
-
```rb
|
225
|
-
Rabarber::Role.all_names
|
226
|
-
```
|
158
|
+
Authorization requires an authenticated user.
|
227
159
|
|
228
|
-
|
160
|
+
### Skip Authorization
|
229
161
|
|
230
|
-
|
162
|
+
You can also selectively skip authorization:
|
231
163
|
|
232
164
|
```rb
|
233
|
-
|
165
|
+
class TicketsController < ApplicationController
|
166
|
+
skip_authorization except: [:create, :update, :destroy]
|
167
|
+
end
|
234
168
|
```
|
235
169
|
|
236
|
-
|
170
|
+
### Authorization Rules
|
237
171
|
|
238
|
-
|
172
|
+
Define access rules using `grant_access`:
|
239
173
|
|
240
174
|
```rb
|
241
|
-
class
|
242
|
-
|
243
|
-
|
244
|
-
end
|
245
|
-
```
|
246
|
-
This adds `.grant_access(action: nil, roles: nil, context: nil, if: nil, unless: nil)` method which allows you to define the authorization rules.
|
175
|
+
class TicketsController < ApplicationController
|
176
|
+
# Controller-wide access
|
177
|
+
grant_access roles: :admin
|
247
178
|
|
248
|
-
|
179
|
+
# Action-specific access
|
180
|
+
grant_access action: :index, roles: [:manager, :support]
|
181
|
+
def index
|
182
|
+
# Accessible to admin, manager, and support roles
|
183
|
+
end
|
249
184
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
grant_access action: :index, roles: [:accountant, :admin]
|
254
|
-
def index
|
255
|
-
@invoices = Invoice.all
|
256
|
-
@invoices = @invoices.paid if current_user.has_role?(:accountant)
|
257
|
-
# ...
|
258
|
-
end
|
259
|
-
|
260
|
-
grant_access action: :destroy, roles: :admin
|
261
|
-
def destroy
|
262
|
-
# ...
|
263
|
-
end
|
185
|
+
grant_access action: :destroy, roles: :admin
|
186
|
+
def destroy
|
187
|
+
# Accessible to admin role only
|
264
188
|
end
|
265
189
|
end
|
266
190
|
```
|
267
|
-
This grants access to `index` action for users with `accountant` or `admin` role, and access to `destroy` action for `admin` users only.
|
268
191
|
|
269
|
-
|
192
|
+
### Additive Rules
|
193
|
+
|
194
|
+
Rules are additive across inheritance chains and for the same actions:
|
270
195
|
|
271
196
|
```rb
|
272
|
-
|
273
|
-
|
274
|
-
grant_access roles: [:admin, :manager]
|
275
|
-
|
276
|
-
grant_access action: :dashboard, roles: :marketer
|
277
|
-
def dashboard
|
278
|
-
# ...
|
279
|
-
end
|
280
|
-
end
|
197
|
+
class BaseController < ApplicationController
|
198
|
+
grant_access roles: :admin # Admin can access everything
|
281
199
|
end
|
282
200
|
|
283
|
-
|
284
|
-
|
285
|
-
grant_access roles: :accountant
|
286
|
-
def index
|
287
|
-
# ...
|
288
|
-
end
|
201
|
+
class InvoicesController < BaseController
|
202
|
+
grant_access roles: :accountant # Accountant can also access InvoicesController (along with admin)
|
289
203
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
end
|
204
|
+
grant_access action: :index, roles: :manager
|
205
|
+
grant_access action: :index, roles: :supervisor
|
206
|
+
# Index is accessible to admin, accountant, manager, and supervisor
|
294
207
|
end
|
295
208
|
```
|
296
|
-
This means that `admin` and `manager` have access to all the actions inside `Crm::BaseController` and its children, while `accountant` role has access only to the actions in `Crm::InvoicesController` and its possible children. Users with `marketer` role can only see the dashboard in this example.
|
297
209
|
|
298
|
-
|
210
|
+
### Unrestricted Access
|
211
|
+
|
212
|
+
Omit roles to allow unrestricted access:
|
299
213
|
|
300
214
|
```rb
|
301
|
-
class
|
302
|
-
grant_access
|
303
|
-
# ...
|
215
|
+
class UnrestrictedController < ApplicationController
|
216
|
+
grant_access # Allow all users
|
304
217
|
end
|
305
218
|
|
306
|
-
class
|
307
|
-
grant_access action: :index
|
308
|
-
|
309
|
-
# ...
|
310
|
-
end
|
219
|
+
class MixedController < ApplicationController
|
220
|
+
grant_access action: :index # Unrestricted index action
|
221
|
+
grant_access action: :show, roles: :member # Restricted show action
|
311
222
|
end
|
312
223
|
```
|
313
224
|
|
314
|
-
|
225
|
+
### Custom Unauthorized Handling
|
315
226
|
|
316
|
-
|
227
|
+
Override `when_unauthorized` method to customize unauthorized access behavior:
|
317
228
|
|
318
|
-
Also keep in mind that rules defined in child classes don't override parent rules but rather add to them:
|
319
229
|
```rb
|
320
|
-
|
321
|
-
|
322
|
-
grant_access roles: :admin
|
323
|
-
# ...
|
324
|
-
end
|
325
|
-
end
|
230
|
+
class ApplicationController < ActionController::Base
|
231
|
+
include Rabarber::Authorization
|
326
232
|
|
327
|
-
|
328
|
-
class InvoicesController < Crm::BaseController
|
329
|
-
grant_access roles: :accountant
|
330
|
-
# ...
|
331
|
-
end
|
332
|
-
end
|
333
|
-
```
|
334
|
-
This means that `Crm::InvoicesController` is still accessible to `admin` but is also accessible to `accountant`.
|
233
|
+
with_authorization
|
335
234
|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
grant_access action: :show, roles: :client, context: -> { Order.find(params[:id]) }
|
344
|
-
grant_access action: :show, roles: :accountant
|
345
|
-
def show
|
346
|
-
# ...
|
347
|
-
end
|
235
|
+
private
|
236
|
+
|
237
|
+
def when_unauthorized
|
238
|
+
# Default behavior: redirect back (HTML) or return 403 (other formats)
|
239
|
+
# Custom behavior example:
|
240
|
+
head :not_found # Hide existence of protected resources
|
348
241
|
end
|
349
242
|
end
|
350
243
|
```
|
351
|
-
This will add rules for `manager` and `admin` roles for all actions in `Crm::OrdersController`, and for `client` and `accountant` roles for the `show` action.
|
352
244
|
|
353
|
-
## Dynamic
|
245
|
+
## Dynamic Rules
|
354
246
|
|
355
|
-
|
247
|
+
Add conditional logic to authorization rules:
|
356
248
|
|
357
249
|
```rb
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
def index
|
363
|
-
# ...
|
364
|
-
end
|
250
|
+
class OrdersController < ApplicationController
|
251
|
+
# Method-based conditions
|
252
|
+
grant_access roles: :manager, if: :company_manager?, unless: :suspended?
|
365
253
|
|
366
|
-
|
254
|
+
# Proc-based conditions
|
255
|
+
grant_access action: :show, roles: :client, if: -> { current_user.company_id == Order.find(params[:id]).company_id }
|
367
256
|
|
368
|
-
|
369
|
-
|
370
|
-
end
|
257
|
+
# Dynamic-only rules (no roles required, can be used with custom policies)
|
258
|
+
grant_access action: :index, if: -> { OrdersPolicy.new(current_user).can_access?(:index) }
|
371
259
|
|
372
|
-
|
373
|
-
current_user.fired?
|
374
|
-
end
|
375
|
-
end
|
376
|
-
end
|
260
|
+
private
|
377
261
|
|
378
|
-
|
379
|
-
|
380
|
-
grant_access roles: :senior_accountant
|
381
|
-
|
382
|
-
grant_access action: :index, roles: [:secretary, :accountant], if: -> { InvoicesPolicy.new(current_user).can_access?(:index) }
|
383
|
-
def index
|
384
|
-
@invoices = Invoice.all
|
385
|
-
@invoices = @invoices.where("total < 10000") if current_user.has_role?(:accountant)
|
386
|
-
@invoices = @invoices.unpaid if current_user.has_role?(:secretary)
|
387
|
-
# ...
|
388
|
-
end
|
389
|
-
|
390
|
-
grant_access action: :show, roles: :accountant, unless: -> { Invoice.find(params[:id]).total > 10_000 }
|
391
|
-
def show
|
392
|
-
# ...
|
393
|
-
end
|
262
|
+
def company_manager?
|
263
|
+
current_user.manager_of?(Company.find(params[:company_id]))
|
394
264
|
end
|
395
|
-
end
|
396
|
-
```
|
397
|
-
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.
|
398
265
|
|
399
|
-
|
400
|
-
|
401
|
-
class InvoicesController < ApplicationController
|
402
|
-
grant_access action: :index, if: -> { current_user.company == Company.find(params[:company_id]) }
|
403
|
-
def index
|
404
|
-
# ...
|
405
|
-
end
|
406
|
-
end
|
407
|
-
```
|
408
|
-
This basically allows you to use Rabarber as a policy-based authorization library by calling your own custom policy within a dynamic rule:
|
409
|
-
```rb
|
410
|
-
class InvoicesController < ApplicationController
|
411
|
-
grant_access action: :index, if: -> { InvoicesPolicy.new(current_user).can_access?(:index) }
|
412
|
-
def index
|
413
|
-
# ...
|
266
|
+
def suspended?
|
267
|
+
current_user.suspended?
|
414
268
|
end
|
415
269
|
end
|
416
270
|
```
|
417
271
|
|
418
|
-
##
|
419
|
-
|
420
|
-
Rabarber supports multi-tenancy by providing a context feature. This allows you to define and authorize roles and rules within a specific context.
|
272
|
+
## Multi-tenancy / Context
|
421
273
|
|
422
|
-
|
274
|
+
All Rabarber methods accept a `context` parameter, allowing you to work with roles within specific scopes rather than globally.
|
423
275
|
|
424
|
-
|
276
|
+
### Contextual Role Assignment
|
425
277
|
|
426
278
|
```rb
|
279
|
+
# Assign roles within a specific model instance
|
427
280
|
user.assign_roles(:owner, context: project)
|
428
|
-
|
429
|
-
```
|
430
|
-
|
431
|
-
Then the roles can be verified:
|
281
|
+
user.assign_roles(:member, context: project)
|
432
282
|
|
433
|
-
|
434
|
-
user.has_role?(:owner, context: project)
|
435
|
-
another_user.has_role?(:member, context: project)
|
436
|
-
```
|
437
|
-
|
438
|
-
A role can also be added using a class as a context, e.g., for project admins who can manage all projects:
|
439
|
-
|
440
|
-
```rb
|
283
|
+
# Assign roles within a model class (e.g., project admin)
|
441
284
|
user.assign_roles(:admin, context: Project)
|
442
|
-
```
|
443
285
|
|
444
|
-
|
445
|
-
|
446
|
-
```rb
|
286
|
+
# Check contextual roles
|
287
|
+
user.has_role?(:owner, context: project)
|
447
288
|
user.has_role?(:admin, context: Project)
|
289
|
+
|
290
|
+
# Get roles within context
|
291
|
+
user.roles(context: project)
|
292
|
+
Rabarber::Role.names(context: Project)
|
448
293
|
```
|
449
294
|
|
450
|
-
|
295
|
+
### Contextual Authorization
|
451
296
|
|
452
297
|
```rb
|
453
298
|
class ProjectsController < ApplicationController
|
299
|
+
# Class-based context
|
454
300
|
grant_access roles: :admin, context: Project
|
455
301
|
|
456
|
-
|
457
|
-
|
458
|
-
# ...
|
459
|
-
end
|
302
|
+
# Instance-based context (method)
|
303
|
+
grant_access action: :show, roles: :member, context: :current_project
|
460
304
|
|
305
|
+
# Instance-based context (proc)
|
461
306
|
grant_access action: :update, roles: :owner, context: -> { Project.find(params[:id]) }
|
462
|
-
def update
|
463
|
-
# ...
|
464
|
-
end
|
465
307
|
|
466
308
|
private
|
467
309
|
|
468
|
-
def
|
469
|
-
Project.find(params[:id])
|
310
|
+
def current_project
|
311
|
+
@current_project ||= Project.find(params[:id])
|
470
312
|
end
|
471
313
|
end
|
472
314
|
```
|
473
315
|
|
474
|
-
|
475
|
-
|
476
|
-
If you want to see all the roles assigned to a user within a specific context, you can use:
|
477
|
-
|
478
|
-
```rb
|
479
|
-
user.roles(context: project)
|
480
|
-
```
|
316
|
+
### Context Migrations
|
481
317
|
|
482
|
-
|
318
|
+
Handle context changes when models are renamed or removed. These are irreversible data migrations.
|
483
319
|
|
484
320
|
```rb
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
## When Unauthorized
|
489
|
-
|
490
|
-
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.
|
321
|
+
# Rename a context class (e.g., when you rename your Project model to Campaign)
|
322
|
+
migrate_authorization_context!("Project", "Campaign")
|
491
323
|
|
492
|
-
|
493
|
-
|
494
|
-
```rb
|
495
|
-
class ApplicationController < ActionController::Base
|
496
|
-
include Rabarber::Authorization
|
497
|
-
|
498
|
-
# ...
|
499
|
-
|
500
|
-
private
|
501
|
-
|
502
|
-
def when_unauthorized
|
503
|
-
head :not_found # pretend the page doesn't exist
|
504
|
-
end
|
505
|
-
end
|
324
|
+
# Remove orphaned context data (e.g., when you delete a model entirely)
|
325
|
+
delete_authorization_context!("DeletedModel")
|
506
326
|
```
|
507
327
|
|
508
|
-
The method can be overridden in different controllers, providing flexibility in handling unauthorized access attempts.
|
509
|
-
|
510
|
-
## Skip Authorization
|
511
|
-
|
512
|
-
To skip authorization, use `.skip_authorization(options = {})` method:
|
513
|
-
|
514
|
-
```rb
|
515
|
-
class TicketsController < ApplicationController
|
516
|
-
skip_authorization only: :index
|
517
|
-
# ...
|
518
|
-
end
|
519
|
-
```
|
520
|
-
|
521
|
-
This method accepts the same options as `skip_before_action` method in Rails.
|
522
|
-
|
523
328
|
## View Helpers
|
524
329
|
|
525
|
-
|
330
|
+
Include view helpers in your application:
|
526
331
|
|
527
332
|
```rb
|
528
333
|
module ApplicationHelper
|
529
334
|
include Rabarber::Helpers
|
530
|
-
# ...
|
531
335
|
end
|
532
336
|
```
|
533
337
|
|
534
|
-
|
338
|
+
Use conditional rendering based on roles:
|
535
339
|
|
536
340
|
```erb
|
537
341
|
<%= visible_to(:admin, :manager) do %>
|
538
|
-
<
|
342
|
+
<div class="admin-panel">
|
343
|
+
<!-- Admin/Manager content -->
|
344
|
+
</div>
|
539
345
|
<% end %>
|
540
|
-
```
|
541
346
|
|
542
|
-
|
543
|
-
|
544
|
-
|
347
|
+
<%= hidden_from(:guest) do %>
|
348
|
+
<div class="member-content">
|
349
|
+
<!-- Content hidden from guests -->
|
350
|
+
</div>
|
545
351
|
<% end %>
|
546
|
-
```
|
547
|
-
|
548
|
-
## Audit Trail
|
549
|
-
|
550
|
-
Rabarber supports audit trail, which provides a record of user access control activity. This feature logs the following events:
|
551
352
|
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
353
|
+
<!-- With context -->
|
354
|
+
<%= visible_to(:owner, context: @project) do %>
|
355
|
+
<button>Delete Project</button>
|
356
|
+
<% end %>
|
357
|
+
```
|
557
358
|
|
558
|
-
##
|
359
|
+
## Contributing
|
559
360
|
|
560
|
-
|
361
|
+
### Getting Help
|
362
|
+
Have a question or need assistance? Open a discussion in our [discussions section](https://github.com/brownboxdev/rabarber/discussions) for:
|
363
|
+
- Usage questions
|
364
|
+
- Implementation guidance
|
365
|
+
- Feature suggestions
|
561
366
|
|
562
|
-
|
367
|
+
### Reporting Issues
|
368
|
+
Found a bug? Please [create an issue](https://github.com/brownboxdev/rabarber/issues) with:
|
369
|
+
- A clear description of the problem
|
370
|
+
- Steps to reproduce the issue
|
371
|
+
- Your environment details (Rails version, Ruby version, etc.)
|
563
372
|
|
564
|
-
|
373
|
+
### Contributing Code
|
374
|
+
Ready to contribute? You can:
|
375
|
+
- Fix bugs by submitting pull requests
|
376
|
+
- Improve documentation
|
377
|
+
- Add new features (please discuss first in our [discussions section](https://github.com/brownboxdev/rabarber/discussions))
|
565
378
|
|
566
|
-
|
567
|
-
- **Contribute a Solution**: Found a fix for the issue? Feel free to create a pull request with your changes.
|
379
|
+
Before contributing, please read the [contributing guidelines](https://github.com/brownboxdev/rabarber/blob/main/CONTRIBUTING.md)
|
568
380
|
|
569
|
-
##
|
381
|
+
## License
|
570
382
|
|
571
|
-
|
383
|
+
The gem is available as open source under the terms of the [MIT License](https://github.com/brownboxdev/rabarber/blob/main/LICENSE.txt).
|
572
384
|
|
573
385
|
## Code of Conduct
|
574
386
|
|
575
|
-
Everyone interacting in the Rabarber project is expected to follow the [code of conduct](https://github.com/
|
387
|
+
Everyone interacting in the Rabarber project is expected to follow the [code of conduct](https://github.com/brownboxdev/rabarber/blob/main/CODE_OF_CONDUCT.md).
|
576
388
|
|
577
|
-
##
|
389
|
+
## Old Versions
|
390
|
+
|
391
|
+
Only the latest major version is supported. Older versions are obsolete and not maintained, but their READMEs are available here for reference:
|
578
392
|
|
579
|
-
|
393
|
+
[v4.x.x](https://github.com/brownboxdev/rabarber/blob/9353e70281971154d5acd70693620197a132c543/README.md) | [v3.x.x](https://github.com/brownboxdev/rabarber/blob/3bb273de7e342004abc7ef07fa4d0a9a3ce3e249/README.md)
|
394
|
+
| [v2.x.x](https://github.com/brownboxdev/rabarber/blob/875b357ea949404ddc3645ad66eddea7ed4e2ee4/README.md) | [v1.x.x](https://github.com/brownboxdev/rabarber/blob/b81428429404e197d70317b763e7b2a21e02c296/README.md)
|