rbacan 0.1.1 → 0.2.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 +5 -5
- data/CHANGELOG.md +50 -0
- data/Gemfile.lock +287 -115
- data/README.md +287 -36
- data/app/models/rbacan/user_role.rb +1 -0
- data/lib/generators/install/templates/copy_to_seeds.rb +13 -17
- data/lib/generators/install/templates/create_permissions.rb +9 -7
- data/lib/generators/install/templates/create_role_permissions.rb +10 -7
- data/lib/generators/install/templates/create_roles.rb +9 -7
- data/lib/generators/install/templates/create_user_roles.rb +11 -8
- data/lib/generators/install/templates/rbacan.rb +27 -0
- data/lib/rbacan/authorization.rb +46 -0
- data/lib/rbacan/engine.rb +9 -3
- data/lib/rbacan/not_authorized.rb +23 -0
- data/lib/rbacan/permittable.rb +74 -28
- data/lib/rbacan/roles_and_permissions.rb +28 -20
- data/lib/rbacan/route_constraint.rb +50 -0
- data/lib/rbacan/version.rb +1 -1
- data/lib/rbacan/view_helpers.rb +14 -0
- data/lib/rbacan.rb +22 -18
- data/rbacan.gemspec +17 -20
- metadata +46 -23
data/README.md
CHANGED
|
@@ -1,81 +1,332 @@
|
|
|
1
|
-
#
|
|
1
|
+
# RBACan
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Role-Based Access Control for Rails. Assign roles to users, attach permissions to roles, and enforce access at the controller, view, and route level.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Setup](#setup)
|
|
9
|
+
- [Configuration](#configuration)
|
|
10
|
+
- [Defining Roles and Permissions](#defining-roles-and-permissions)
|
|
11
|
+
- [Role Management](#role-management)
|
|
12
|
+
- [Checking Permissions](#checking-permissions)
|
|
13
|
+
- [Querying Users by Role or Permission](#querying-users-by-role-or-permission)
|
|
14
|
+
- [Controller Authorization](#controller-authorization)
|
|
15
|
+
- [View Helpers](#view-helpers)
|
|
16
|
+
- [Route Constraints](#route-constraints)
|
|
17
|
+
- [Development](#development)
|
|
18
|
+
- [Contributing](#contributing)
|
|
19
|
+
- [License](#license)
|
|
20
|
+
|
|
21
|
+
---
|
|
4
22
|
|
|
5
23
|
## Installation
|
|
6
24
|
|
|
7
|
-
Add
|
|
25
|
+
Add to your Gemfile:
|
|
8
26
|
|
|
9
27
|
```ruby
|
|
10
28
|
gem 'rbacan'
|
|
11
29
|
```
|
|
12
30
|
|
|
13
|
-
|
|
31
|
+
Then run:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
bundle install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Setup
|
|
40
|
+
|
|
41
|
+
**1. Run the generator**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
rails generate rbacan:install
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This copies four migrations, a seed helper file, and an initializer into your app.
|
|
48
|
+
|
|
49
|
+
**2. Migrate**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
rails db:migrate
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**3. Include the concern in your user model**
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
class User < ApplicationRecord
|
|
59
|
+
include Rbacan::Permittable
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**4. Define your roles and permissions in `db/seeds.rb`**
|
|
64
|
+
|
|
65
|
+
Open `db/copy_to_seeds.rb` (created by the generator) and copy its contents into your `db/seeds.rb`. Fill in your roles and permissions, then run:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
rails db:seed
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
The generator creates `config/initializers/rbacan.rb`. All options are optional — the defaults work for a standard Devise setup.
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
Rbacan.configure do |config|
|
|
79
|
+
# Your user model name (default: "User")
|
|
80
|
+
config.permittable_class = "User"
|
|
81
|
+
|
|
82
|
+
# Override model class names if you have custom implementations
|
|
83
|
+
# config.role_class = "Rbacan::Role"
|
|
84
|
+
# config.permission_class = "Rbacan::Permission"
|
|
85
|
+
# config.user_role_class = "Rbacan::UserRole"
|
|
86
|
+
# config.role_permission_class = "Rbacan::RolePermission"
|
|
87
|
+
|
|
88
|
+
# Override table names if needed
|
|
89
|
+
# config.role_table = "roles"
|
|
90
|
+
# config.permission_table = "permissions"
|
|
91
|
+
# config.user_role_table = "user_roles"
|
|
92
|
+
# config.role_permission_table = "role_permissions"
|
|
93
|
+
|
|
94
|
+
# How to handle unauthorized access (see Controller Authorization)
|
|
95
|
+
# config.unauthorized_handler = :raise # default — raises Rbacan::NotAuthorized
|
|
96
|
+
# config.unauthorized_handler = :redirect # redirects to unauthorized_redirect_path
|
|
97
|
+
# config.unauthorized_handler = ->(controller, permission:, role:) {
|
|
98
|
+
# controller.render plain: "Forbidden", status: :forbidden
|
|
99
|
+
# }
|
|
100
|
+
|
|
101
|
+
# Redirect path used when unauthorized_handler is :redirect (default: "/")
|
|
102
|
+
# config.unauthorized_redirect_path = "/login"
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Defining Roles and Permissions
|
|
109
|
+
|
|
110
|
+
Use the `Rbacan::RolesAndPermissions` module in your seeds. All methods are idempotent — safe to run multiple times.
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
# db/seeds.rb
|
|
114
|
+
|
|
115
|
+
roles = ["admin", "moderator", "viewer"]
|
|
116
|
+
permissions = ["edit_post", "delete_post", "publish_post", "view_dashboard"]
|
|
117
|
+
|
|
118
|
+
Rbacan::RolesAndPermissions.create_roles(roles)
|
|
119
|
+
Rbacan::RolesAndPermissions.create_permissions(permissions)
|
|
120
|
+
|
|
121
|
+
# Assign permissions to each role
|
|
122
|
+
Rbacan::RolesAndPermissions.assign_permissions_to_role("admin", permissions)
|
|
123
|
+
Rbacan::RolesAndPermissions.assign_permissions_to_role("moderator", ["edit_post", "publish_post"])
|
|
124
|
+
Rbacan::RolesAndPermissions.assign_permissions_to_role("viewer", ["view_dashboard"])
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
You can also create roles and permissions programmatically at runtime:
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
Rbacan.create_role("editor")
|
|
131
|
+
Rbacan.create_permission("manage_comments")
|
|
132
|
+
Rbacan.assign_permission_to_role("editor", "manage_comments")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Role Management
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
user = User.find(1)
|
|
141
|
+
|
|
142
|
+
# Assign a role — idempotent, safe to call multiple times
|
|
143
|
+
user.assign_role("admin")
|
|
144
|
+
user.assign_role(:moderator)
|
|
145
|
+
|
|
146
|
+
# Remove a role
|
|
147
|
+
user.remove_role("moderator")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Checking Permissions
|
|
153
|
+
|
|
154
|
+
### On a single permission
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
user.can?("edit_post") # => true or false
|
|
158
|
+
user.can?(:edit_post) # symbols work too
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Checking all permissions at once
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
# Returns true only if the user has every listed permission
|
|
165
|
+
user.can_all?(:edit_post, :delete_post, :publish_post)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Checking roles
|
|
14
169
|
|
|
15
|
-
|
|
170
|
+
```ruby
|
|
171
|
+
# Does the user have this specific role?
|
|
172
|
+
user.has_role?(:admin)
|
|
173
|
+
|
|
174
|
+
# Does the user have at least one of these roles?
|
|
175
|
+
user.has_any_role?(:admin, :moderator)
|
|
176
|
+
```
|
|
16
177
|
|
|
17
|
-
|
|
178
|
+
---
|
|
18
179
|
|
|
19
|
-
|
|
180
|
+
## Querying Users by Role or Permission
|
|
20
181
|
|
|
21
|
-
|
|
182
|
+
Use these ActiveRecord scopes to query your user table.
|
|
22
183
|
|
|
23
184
|
```ruby
|
|
24
|
-
|
|
185
|
+
# All users with the admin role
|
|
186
|
+
User.with_role(:admin)
|
|
187
|
+
|
|
188
|
+
# All users who have a given permission (via any of their roles)
|
|
189
|
+
User.with_permission(:publish_post)
|
|
190
|
+
|
|
191
|
+
# Combine with other scopes
|
|
192
|
+
User.with_role(:moderator).where(active: true)
|
|
25
193
|
```
|
|
26
194
|
|
|
27
|
-
|
|
28
|
-
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Controller Authorization
|
|
29
198
|
|
|
30
|
-
|
|
199
|
+
Include `Rbacan::Authorization` in your `ApplicationController` (or any specific controller):
|
|
31
200
|
|
|
32
201
|
```ruby
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
202
|
+
class ApplicationController < ActionController::Base
|
|
203
|
+
include Rbacan::Authorization
|
|
204
|
+
end
|
|
36
205
|
```
|
|
37
206
|
|
|
38
|
-
|
|
207
|
+
Then use `authorize!` or `authorize_role!` as `before_action` callbacks:
|
|
39
208
|
|
|
40
|
-
|
|
209
|
+
```ruby
|
|
210
|
+
class PostsController < ApplicationController
|
|
211
|
+
before_action -> { authorize!(:edit_post) }, only: [:edit, :update]
|
|
212
|
+
before_action -> { authorize!(:delete_post) }, only: [:destroy]
|
|
213
|
+
before_action -> { authorize_role!(:admin) }, only: [:admin_index]
|
|
214
|
+
end
|
|
215
|
+
```
|
|
41
216
|
|
|
42
|
-
|
|
217
|
+
Or call them directly inside an action:
|
|
43
218
|
|
|
44
|
-
|
|
219
|
+
```ruby
|
|
220
|
+
def destroy
|
|
221
|
+
authorize!(:delete_post)
|
|
222
|
+
@post.destroy
|
|
223
|
+
end
|
|
224
|
+
```
|
|
45
225
|
|
|
46
|
-
|
|
226
|
+
### Handling unauthorized access
|
|
47
227
|
|
|
48
|
-
|
|
228
|
+
The default behavior raises `Rbacan::NotAuthorized`. You can rescue it globally:
|
|
49
229
|
|
|
50
|
-
|
|
230
|
+
```ruby
|
|
231
|
+
# app/controllers/application_controller.rb
|
|
232
|
+
rescue_from Rbacan::NotAuthorized, with: :handle_unauthorized
|
|
51
233
|
|
|
52
|
-
|
|
234
|
+
private
|
|
53
235
|
|
|
54
|
-
|
|
236
|
+
def handle_unauthorized(exception)
|
|
237
|
+
render plain: exception.message, status: :forbidden
|
|
238
|
+
end
|
|
239
|
+
```
|
|
55
240
|
|
|
56
|
-
|
|
241
|
+
Or configure a different handler in the initializer:
|
|
57
242
|
|
|
58
243
|
```ruby
|
|
59
|
-
|
|
60
|
-
|
|
244
|
+
# Redirect to a path instead of raising
|
|
245
|
+
config.unauthorized_handler = :redirect
|
|
246
|
+
config.unauthorized_redirect_path = "/login"
|
|
247
|
+
|
|
248
|
+
# Or use a fully custom lambda
|
|
249
|
+
config.unauthorized_handler = ->(controller, permission:, role:) {
|
|
250
|
+
controller.render json: { error: "Forbidden" }, status: :forbidden
|
|
251
|
+
}
|
|
61
252
|
```
|
|
62
253
|
|
|
63
|
-
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## View Helpers
|
|
257
|
+
|
|
258
|
+
`Rbacan::ViewHelpers` is automatically included in all views. Use `authorized?` to conditionally render content:
|
|
259
|
+
|
|
260
|
+
```erb
|
|
261
|
+
<% authorized?(:delete_post) do %>
|
|
262
|
+
<%= link_to "Delete", post_path(@post), data: { turbo_method: :delete } %>
|
|
263
|
+
<% end %>
|
|
264
|
+
|
|
265
|
+
<% authorized?(:publish_post) do %>
|
|
266
|
+
<%= button_to "Publish", publish_post_path(@post) %>
|
|
267
|
+
<% end %>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
You can also use `has_role?` and `has_any_role?` directly in views since they are instance methods on the user:
|
|
271
|
+
|
|
272
|
+
```erb
|
|
273
|
+
<% if current_user.has_role?(:admin) %>
|
|
274
|
+
<%= link_to "Admin Panel", admin_root_path %>
|
|
275
|
+
<% end %>
|
|
276
|
+
|
|
277
|
+
<% if current_user.has_any_role?(:admin, :moderator) %>
|
|
278
|
+
<%= link_to "Moderation Queue", moderation_path %>
|
|
279
|
+
<% end %>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Route Constraints
|
|
285
|
+
|
|
286
|
+
Restrict access to entire route namespaces based on role or permission. Add constraints in `config/routes.rb`:
|
|
287
|
+
|
|
288
|
+
```ruby
|
|
289
|
+
# Restrict by role
|
|
290
|
+
constraints Rbacan::RouteConstraint.new(role: :admin) do
|
|
291
|
+
namespace :admin do
|
|
292
|
+
resources :users
|
|
293
|
+
resources :roles
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Restrict by permission
|
|
298
|
+
constraints Rbacan::RouteConstraint.new(permission: :access_dashboard) do
|
|
299
|
+
get "/dashboard", to: "dashboard#index"
|
|
300
|
+
end
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The constraint reads the current user from the Warden session (used by Devise). For apps using a manual session, it falls back to looking up `session[:user_id]`.
|
|
304
|
+
|
|
305
|
+
---
|
|
64
306
|
|
|
65
307
|
## Development
|
|
66
308
|
|
|
67
|
-
|
|
309
|
+
```bash
|
|
310
|
+
bin/setup # install dependencies
|
|
311
|
+
bundle exec rake spec # run all tests
|
|
312
|
+
bundle exec rspec spec/rbacan_spec.rb # run a specific file
|
|
313
|
+
bundle exec rake install # install gem locally
|
|
314
|
+
```
|
|
68
315
|
|
|
69
|
-
To
|
|
316
|
+
To release a new version:
|
|
70
317
|
|
|
71
|
-
|
|
318
|
+
1. Bump the version in `lib/rbacan/version.rb`
|
|
319
|
+
2. `gem build rbacan.gemspec`
|
|
320
|
+
3. `gem push rbacan-<version>.gem`
|
|
72
321
|
|
|
73
|
-
|
|
322
|
+
---
|
|
74
323
|
|
|
75
|
-
##
|
|
324
|
+
## Contributing
|
|
76
325
|
|
|
77
|
-
|
|
326
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/hamdi777/RBACan.
|
|
78
327
|
|
|
79
|
-
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## License
|
|
80
331
|
|
|
81
|
-
|
|
332
|
+
Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -1,26 +1,22 @@
|
|
|
1
|
-
#Copy the content of this file into your seeds.rb and
|
|
1
|
+
# Copy the content of this file into your seeds.rb and uncomment what you need.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
#
|
|
3
|
+
# Define the roles you want, e.g.:
|
|
4
|
+
# roles = ["admin", "moderator", "viewer"]
|
|
5
5
|
roles = []
|
|
6
|
-
# create roles
|
|
7
6
|
Rbacan::RolesAndPermissions.create_roles(roles)
|
|
8
7
|
|
|
9
8
|
|
|
10
|
-
#
|
|
9
|
+
# Define the permissions you want, e.g.:
|
|
10
|
+
# permissions = ["edit_post", "delete_post", "publish_post"]
|
|
11
11
|
permissions = []
|
|
12
|
-
# create permissions
|
|
13
12
|
Rbacan::RolesAndPermissions.create_permissions(permissions)
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
role_permissions = []
|
|
20
|
-
Rbacan::RolesAndPermissions.assign_permissions_to_role(
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# role_name = role.name
|
|
25
|
-
# Rbacan::RolesAndPermissions.assign_permissions_to_role(role_name, role_permissions)
|
|
26
|
-
# end
|
|
15
|
+
# Assign permissions to a role. Repeat this block for each role you want to configure.
|
|
16
|
+
# Replace "admin" with the role name, and list the permissions it should have.
|
|
17
|
+
#
|
|
18
|
+
# role_permissions = ["edit_post", "delete_post", "publish_post"]
|
|
19
|
+
# Rbacan::RolesAndPermissions.assign_permissions_to_role("admin", role_permissions)
|
|
20
|
+
#
|
|
21
|
+
# To assign all permissions to a role:
|
|
22
|
+
# Rbacan::RolesAndPermissions.assign_permissions_to_role("admin", permissions)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
class CreatePermissions < ActiveRecord::Migration[
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
end
|
|
1
|
+
class CreatePermissions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :permissions do |t|
|
|
4
|
+
t.string :name, null: false
|
|
5
|
+
|
|
6
|
+
t.timestamps
|
|
8
7
|
end
|
|
8
|
+
|
|
9
|
+
add_index :permissions, :name, unique: true
|
|
10
|
+
end
|
|
9
11
|
end
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
class CreateRolePermissions < ActiveRecord::Migration[
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
class CreateRolePermissions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :role_permissions do |t|
|
|
4
|
+
t.references :role, null: false, index: true, foreign_key: { to_table: :roles, on_delete: :cascade }
|
|
5
|
+
t.references :permission, null: false, index: true, foreign_key: { to_table: :permissions, on_delete: :cascade }
|
|
6
|
+
|
|
7
|
+
t.timestamps
|
|
8
8
|
end
|
|
9
|
+
|
|
10
|
+
add_index :role_permissions, [:role_id, :permission_id], unique: true
|
|
11
|
+
end
|
|
9
12
|
end
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
class CreateRoles < ActiveRecord::Migration[
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
end
|
|
1
|
+
class CreateRoles < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :roles do |t|
|
|
4
|
+
t.string :name, null: false
|
|
5
|
+
|
|
6
|
+
t.timestamps
|
|
8
7
|
end
|
|
8
|
+
|
|
9
|
+
add_index :roles, :name, unique: true
|
|
10
|
+
end
|
|
9
11
|
end
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
class CreateUserRoles < ActiveRecord::Migration[
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
end
|
|
1
|
+
class CreateUserRoles < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :user_roles do |t|
|
|
4
|
+
t.references :role, null: false, index: true, foreign_key: { to_table: :roles, on_delete: :cascade }
|
|
5
|
+
t.bigint :user_id, null: false
|
|
6
|
+
|
|
7
|
+
t.timestamps
|
|
9
8
|
end
|
|
9
|
+
|
|
10
|
+
add_index :user_roles, :user_id
|
|
11
|
+
add_index :user_roles, [:user_id, :role_id], unique: true
|
|
12
|
+
end
|
|
10
13
|
end
|
|
@@ -1,3 +1,30 @@
|
|
|
1
1
|
Rbacan.configure do |config|
|
|
2
|
+
# The name of your user model (default: "User")
|
|
2
3
|
# config.permittable_class = "User"
|
|
4
|
+
|
|
5
|
+
# Override AR model class names if you have custom models
|
|
6
|
+
# config.role_class = "Rbacan::Role"
|
|
7
|
+
# config.permission_class = "Rbacan::Permission"
|
|
8
|
+
# config.user_role_class = "Rbacan::UserRole"
|
|
9
|
+
# config.role_permission_class = "Rbacan::RolePermission"
|
|
10
|
+
|
|
11
|
+
# Override table names if needed
|
|
12
|
+
# config.role_table = "roles"
|
|
13
|
+
# config.permission_table = "permissions"
|
|
14
|
+
# config.user_role_table = "user_roles"
|
|
15
|
+
# config.role_permission_table = "role_permissions"
|
|
16
|
+
|
|
17
|
+
# Authorization failure handling:
|
|
18
|
+
# :raise — raises Rbacan::NotAuthorized (default)
|
|
19
|
+
# :redirect — redirects to unauthorized_redirect_path
|
|
20
|
+
# lambda — called with (controller, permission:, role:)
|
|
21
|
+
#
|
|
22
|
+
# config.unauthorized_handler = :raise
|
|
23
|
+
# config.unauthorized_handler = :redirect
|
|
24
|
+
# config.unauthorized_handler = ->(controller, permission:, role:) {
|
|
25
|
+
# controller.render plain: "Forbidden", status: :forbidden
|
|
26
|
+
# }
|
|
27
|
+
|
|
28
|
+
# Path to redirect to when unauthorized_handler is :redirect
|
|
29
|
+
# config.unauthorized_redirect_path = "/"
|
|
3
30
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'active_support/concern'
|
|
2
|
+
|
|
3
|
+
module Rbacan
|
|
4
|
+
module Authorization
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
helper_method :authorized? if respond_to?(:helper_method)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Calls the unauthorized handler if current_user lacks the given permission.
|
|
12
|
+
def authorize!(permission)
|
|
13
|
+
unless current_user&.can?(permission.to_s)
|
|
14
|
+
_handle_unauthorized(permission: permission.to_s)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Calls the unauthorized handler if current_user lacks the given role.
|
|
19
|
+
def authorize_role!(role)
|
|
20
|
+
unless current_user&.has_role?(role.to_s)
|
|
21
|
+
_handle_unauthorized(role: role.to_s)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def _handle_unauthorized(permission: nil, role: nil)
|
|
28
|
+
handler = Rbacan.unauthorized_handler
|
|
29
|
+
|
|
30
|
+
case handler
|
|
31
|
+
when :raise
|
|
32
|
+
raise Rbacan::NotAuthorized.new(nil, permission: permission, role: role)
|
|
33
|
+
when :redirect
|
|
34
|
+
redirect_to Rbacan.unauthorized_redirect_path,
|
|
35
|
+
alert: Rbacan::NotAuthorized.new(nil, permission: permission, role: role).message
|
|
36
|
+
else
|
|
37
|
+
if handler.respond_to?(:call)
|
|
38
|
+
handler.call(self, permission: permission, role: role)
|
|
39
|
+
else
|
|
40
|
+
raise ArgumentError,
|
|
41
|
+
"Rbacan.unauthorized_handler must be :raise, :redirect, or a callable. Got: #{handler.inspect}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/rbacan/engine.rb
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
module Rbacan
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
require "rails/all"
|
|
3
|
+
class Engine < ::Rails::Engine
|
|
4
|
+
engine_name 'rbacan'
|
|
5
|
+
|
|
6
|
+
initializer "rbacan.view_helpers" do
|
|
7
|
+
ActiveSupport.on_load(:action_view) do
|
|
8
|
+
include Rbacan::ViewHelpers
|
|
9
|
+
end
|
|
5
10
|
end
|
|
11
|
+
end
|
|
6
12
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Rbacan
|
|
2
|
+
class NotAuthorized < StandardError
|
|
3
|
+
attr_reader :permission, :role
|
|
4
|
+
|
|
5
|
+
def initialize(message = nil, permission: nil, role: nil)
|
|
6
|
+
@permission = permission
|
|
7
|
+
@role = role
|
|
8
|
+
super(message || default_message)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def default_message
|
|
14
|
+
if permission
|
|
15
|
+
"Not authorized: missing permission '#{permission}'"
|
|
16
|
+
elsif role
|
|
17
|
+
"Not authorized: missing role '#{role}'"
|
|
18
|
+
else
|
|
19
|
+
"Not authorized"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|