rails_iam 0.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 +7 -0
- data/CHANGELOG.md +23 -0
- data/CONTRIBUTING.md +145 -0
- data/LICENSE +20 -0
- data/README.md +192 -0
- data/Rakefile +14 -0
- data/app/controllers/concerns/rails_iam/auth_concern.rb +60 -0
- data/app/controllers/concerns/rails_iam/authentication/authenticatable.rb +44 -0
- data/app/controllers/concerns/rails_iam/authorization/authorizable.rb +170 -0
- data/app/controllers/rails_iam/application_controller.rb +7 -0
- data/app/controllers/rails_iam/auth_controller.rb +113 -0
- data/app/helpers/rails_iam/config_loader.rb +71 -0
- data/app/models/concerns/rails_iam/auditable.rb +57 -0
- data/app/models/concerns/rails_iam/user_concern.rb +59 -0
- data/app/models/rails_iam/application_record.rb +8 -0
- data/app/models/rails_iam/permission.rb +14 -0
- data/app/models/rails_iam/refresh_token.rb +7 -0
- data/app/models/rails_iam/role.rb +14 -0
- data/app/models/rails_iam/role_permission.rb +20 -0
- data/app/models/rails_iam/user.rb +16 -0
- data/app/models/rails_iam/user_denied_permission.rb +12 -0
- data/app/models/rails_iam/user_permission.rb +12 -0
- data/app/models/rails_iam/user_role.rb +12 -0
- data/app/services/application_service.rb +4 -0
- data/app/services/rails_iam/authentication/authenticated_user.rb +13 -0
- data/app/services/rails_iam/authentication/authenticator.rb +39 -0
- data/app/services/rails_iam/authentication/current.rb +9 -0
- data/app/services/rails_iam/authentication/exceptions/authentication_error.rb +10 -0
- data/app/services/rails_iam/authentication/jwt_decoder.rb +38 -0
- data/app/services/rails_iam/authentication/jwt_encoder.rb +45 -0
- data/app/services/rails_iam/authentication/login.rb +67 -0
- data/app/services/rails_iam/authentication/logout.rb +35 -0
- data/app/services/rails_iam/authentication/refresh_token_encoder.rb +11 -0
- data/app/services/rails_iam/authentication/skip_authentication_rule.rb +20 -0
- data/app/services/rails_iam/authentication/token_extractor.rb +64 -0
- data/app/services/rails_iam/authorization/authorization_context.rb +41 -0
- data/app/services/rails_iam/authorization/authorization_rule.rb +38 -0
- data/app/services/rails_iam/authorization/authorizer.rb +35 -0
- data/app/services/rails_iam/authorization/cache.rb +39 -0
- data/app/services/rails_iam/authorization/denied_permission_checker.rb +33 -0
- data/app/services/rails_iam/authorization/exceptions/authorization_error.rb +10 -0
- data/app/services/rails_iam/authorization/exceptions/missing_permission_error.rb +35 -0
- data/app/services/rails_iam/authorization/exceptions/missing_role_error.rb +35 -0
- data/app/services/rails_iam/authorization/permission_checker.rb +42 -0
- data/app/services/rails_iam/authorization/permission_resolver.rb +70 -0
- data/app/services/rails_iam/authorization/permission_sources/role_permission_source.rb +37 -0
- data/app/services/rails_iam/authorization/permission_sources/user_denied_permission_source.rb +29 -0
- data/app/services/rails_iam/authorization/permission_sources/user_permission_source.rb +28 -0
- data/app/services/rails_iam/authorization/resolved_permissions.rb +63 -0
- data/app/services/rails_iam/authorization/role_checker.rb +35 -0
- data/app/services/rails_iam/authorization/role_resolver.rb +30 -0
- data/app/services/rails_iam/authorization/skip_authorization_rule.rb +19 -0
- data/app/services/rails_iam/rails_iam_service.rb +11 -0
- data/config/routes.rb +25 -0
- data/lib/generators/rails_iam/install_generator.rb +49 -0
- data/lib/generators/rails_iam/templates/create_rails_iam_tables.rb.erb +119 -0
- data/lib/generators/rails_iam/templates/initializer.rb +162 -0
- data/lib/rails_iam/config/authentication.rb +65 -0
- data/lib/rails_iam/config/authorization.rb +36 -0
- data/lib/rails_iam/config/jwt.rb +27 -0
- data/lib/rails_iam/configuration.rb +30 -0
- data/lib/rails_iam/controller_dsl.rb +41 -0
- data/lib/rails_iam/engine.rb +34 -0
- data/lib/rails_iam/model_dsl.rb +13 -0
- data/lib/rails_iam/version.rb +5 -0
- data/lib/rails_iam.rb +27 -0
- data/lib/tasks/rails_iam_tasks.rake +6 -0
- data/rails_iam.gemspec +46 -0
- metadata +161 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: bd559a06706a5935fa65efc0ddcf6441d82ed991ab95e02849f7eec59ae4a86f
|
|
4
|
+
data.tar.gz: ab21b61c3936c671742bdba0eeac35ca7676d0514d24f8d767c00832551ac0f5
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6c8fb9c39a9aaae9c15ef39c0be553f7fe6ffa9e271e5d05fb91ba73e29cc46bd9bf58a832e5a0c8e8b16a8a068530da53ed69c4a9bb8aa828486fc6c9807ed7
|
|
7
|
+
data.tar.gz: 7354acf8d1b2aa9d90355674350d853bc176e3242bd1ed2df90170b4f0f91041c78f375a8dfb686d786c4efe551f989f3b46b0f3ff9a2640816a03de9b80076e
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-08-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- JWT authentication
|
|
10
|
+
- Refresh token authentication
|
|
11
|
+
- Access token authentication via HttpOnly cookies
|
|
12
|
+
- Access token authentication via Bearer tokens
|
|
13
|
+
- Refresh token rotation
|
|
14
|
+
- Database-driven RBAC (Role-Based Access Control)
|
|
15
|
+
- PBAC (Permission-Based Access Control)
|
|
16
|
+
- Direct user permissions
|
|
17
|
+
- User denied permissions
|
|
18
|
+
- Automatic permission inference with `authorize_resource`
|
|
19
|
+
- Endpoint-first authorization
|
|
20
|
+
- Authorization caching
|
|
21
|
+
- Authentication and authorization generators
|
|
22
|
+
- RSpec testing guide
|
|
23
|
+
- Comprehensive Wiki documentation
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Contributing to RailsIAM
|
|
2
|
+
|
|
3
|
+
First of all, thank you for your interest in contributing to RailsIAM! 🎉
|
|
4
|
+
|
|
5
|
+
Whether you're reporting a bug, improving documentation, fixing a typo, or implementing a new feature, your contribution is greatly appreciated.
|
|
6
|
+
|
|
7
|
+
## Before You Start
|
|
8
|
+
|
|
9
|
+
Please check the existing:
|
|
10
|
+
|
|
11
|
+
- [Issues](https://github.com/salmanx/rails_iam/issues)
|
|
12
|
+
- [Pull Requests](https://github.com/salmanx/rails_iam/pulls)
|
|
13
|
+
|
|
14
|
+
to make sure your idea or bug hasn't already been discussed.
|
|
15
|
+
|
|
16
|
+
If you're planning a significant feature or architectural change, please open an issue first so we can discuss the design before implementation.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Reporting Bugs
|
|
21
|
+
|
|
22
|
+
When opening a bug report, please include as much information as possible:
|
|
23
|
+
|
|
24
|
+
- Rails version
|
|
25
|
+
- Ruby version
|
|
26
|
+
- RailsIAM version
|
|
27
|
+
- Authentication strategy
|
|
28
|
+
- Database adapter
|
|
29
|
+
- Steps to reproduce
|
|
30
|
+
- Expected behavior
|
|
31
|
+
- Actual behavior
|
|
32
|
+
- A minimal reproducible example if possible
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
# Feature Requests
|
|
37
|
+
|
|
38
|
+
RailsIAM aims to remain simple, predictable, and database-driven.
|
|
39
|
+
|
|
40
|
+
If you'd like to propose a new feature:
|
|
41
|
+
|
|
42
|
+
1. Open an issue describing the problem you're trying to solve.
|
|
43
|
+
2. Explain your proposed solution.
|
|
44
|
+
3. Wait for discussion before starting implementation.
|
|
45
|
+
|
|
46
|
+
Features that increase complexity without broad usefulness may not be accepted.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
# Documentation
|
|
51
|
+
|
|
52
|
+
Documentation improvements are always welcome.
|
|
53
|
+
|
|
54
|
+
This includes:
|
|
55
|
+
|
|
56
|
+
- README improvements
|
|
57
|
+
- Wiki pages
|
|
58
|
+
- API examples
|
|
59
|
+
- Typo fixes
|
|
60
|
+
- Better explanations
|
|
61
|
+
- Additional authorization examples
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
# Development
|
|
66
|
+
|
|
67
|
+
Clone the repository:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
git clone https://github.com/salmanx/rails_iam.git
|
|
71
|
+
cd rails_iam
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Install dependencies:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
bundle install
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Run the test suite:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
bundle exec rspec
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Please ensure all tests pass before submitting a Pull Request.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
# Pull Requests
|
|
91
|
+
|
|
92
|
+
Please follow these guidelines:
|
|
93
|
+
|
|
94
|
+
- Create a new branch from `main`
|
|
95
|
+
- Keep Pull Requests focused on a single change
|
|
96
|
+
- Follow the existing coding style
|
|
97
|
+
- Add or update tests when applicable
|
|
98
|
+
- Update documentation when behavior changes
|
|
99
|
+
- Update the CHANGELOG for user-facing changes
|
|
100
|
+
- Keep commit messages clear and descriptive
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Add support for custom authorization strategies
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
# Code Style
|
|
111
|
+
|
|
112
|
+
Please follow the existing project conventions:
|
|
113
|
+
|
|
114
|
+
- Follow Ruby and Rails style guidelines
|
|
115
|
+
- Prefer small, focused classes
|
|
116
|
+
- Keep public APIs backward compatible whenever possible
|
|
117
|
+
- Write expressive, readable code
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
# Testing
|
|
122
|
+
|
|
123
|
+
Every new feature or bug fix should include appropriate tests whenever possible.
|
|
124
|
+
|
|
125
|
+
RailsIAM uses RSpec for testing.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
# Versioning
|
|
130
|
+
|
|
131
|
+
Please do **not** update the gem version in your Pull Request.
|
|
132
|
+
|
|
133
|
+
Version numbers are managed by the project maintainers during release.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
# Questions
|
|
138
|
+
|
|
139
|
+
If something isn't clear, feel free to open a GitHub Discussion or Issue.
|
|
140
|
+
|
|
141
|
+
Constructive feedback and questions are always welcome.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
Thank you for helping make RailsIAM better! 🚀
|
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright Salman Mahmud
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# RailsIAM
|
|
2
|
+
|
|
3
|
+
JWT • RBAC • PBAC • IAM • Roles • Permissions
|
|
4
|
+
|
|
5
|
+
RailsIAM is a database-driven Identity & Access Management (IAM) engine for your Rails applications. It follows an endpoint-first authorization model where permissions are treated as data, not code. Roles and permission assignments are stored in the database, allowing access control to evolve without changing authorization logic. RailsIAM includes an optional JWT authentication implementation for applications that need it, while its authorization layer works with Devise or any authentication system capable of providing the authenticated user.
|
|
6
|
+
|
|
7
|
+
## Why RailsIAM exists?
|
|
8
|
+
|
|
9
|
+
RailsIAM is not trying to reinvent authentication or authorization.
|
|
10
|
+
|
|
11
|
+
The Rails ecosystem already has excellent solutions for these problems. You can build authentication yourself, use Devise, write your own authorization layer, or choose from several existing gems.
|
|
12
|
+
|
|
13
|
+
RailsIAM exists for a different reason: **to save your time from rebuilding the same foundation every time.**
|
|
14
|
+
|
|
15
|
+
Every Rails application eventually needs the same building blocks:
|
|
16
|
+
|
|
17
|
+
- Who is the authenticated user?
|
|
18
|
+
- What roles does this user have?
|
|
19
|
+
- What permissions should they have?
|
|
20
|
+
- Which controller actions can they access?
|
|
21
|
+
- How do we manage access changes without touching application code or redeploying the application?
|
|
22
|
+
- Who gives permissions to whom?
|
|
23
|
+
|
|
24
|
+
RailsIAM gives you that foundation out of the box, so team can focus on building business features instead of spending **_another sprint works_** wiring authentication, roles, permissions, database relationships, authorization checks, auditing and error handling.
|
|
25
|
+
|
|
26
|
+
## How to use it?
|
|
27
|
+
|
|
28
|
+
Most Rails applications eventually need to answer one important question:
|
|
29
|
+
|
|
30
|
+
> **_"Is this authenticated user allowed to perform this action?"_**
|
|
31
|
+
|
|
32
|
+
With RailsIAM, authorization is declared at the point where requests enter application — controller actions. There are no policy classes to maintain, no authorization logic scattered across models, and no condition-heavy permission checks inside controllers.
|
|
33
|
+
|
|
34
|
+
Once roles and permissions are stored in the database, protecting an action becomes as simple as declaring who can access it.
|
|
35
|
+
|
|
36
|
+
Whether team building an API-only application or a traditional Rails application with server-rendered views, RailsIAM keeps authorization rules explicit, centralized, and independent from authentication strategy.
|
|
37
|
+
|
|
38
|
+
### Role-based authorization
|
|
39
|
+
|
|
40
|
+
Allow administrators to access every action in the controller.
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
class UsersController < ApplicationController
|
|
44
|
+
|
|
45
|
+
# admin role can access to *all* the actions
|
|
46
|
+
authorize roles: :admin
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
### Permission-based authorization
|
|
54
|
+
|
|
55
|
+
Protect a single endpoint using a permission stored in the database. No matter what your role is.
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
class UsersController < ApplicationController
|
|
59
|
+
|
|
60
|
+
# people who has "user:show" permission can access to show action
|
|
61
|
+
authorize permissions: "user:show", only: :show
|
|
62
|
+
|
|
63
|
+
def show; end
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Some endpoints should remain publicly accessible while the rest of the controller requires authorization.
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
class ProductsController < ApplicationController
|
|
71
|
+
|
|
72
|
+
authorize permissions: "product:manage", except: [:index, :show]
|
|
73
|
+
|
|
74
|
+
def index; end # Public
|
|
75
|
+
def show; end # Public
|
|
76
|
+
def create; end # Requires product:manage
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
### Role + Permission
|
|
83
|
+
|
|
84
|
+
Require both a specific role **and** a permission.
|
|
85
|
+
|
|
86
|
+
This is useful when only members of a department should be able to perform an operation, even if another role happens to own the same permission.
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
class UsersController < ApplicationController
|
|
90
|
+
# admin and manager roles can access to all the action
|
|
91
|
+
authorize roles: [:admin, :manager]
|
|
92
|
+
# sales role can only access to :show
|
|
93
|
+
authorize roles: :sales, permissions: "user:show", only: :show
|
|
94
|
+
|
|
95
|
+
def show; end
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### Convention over configuration
|
|
102
|
+
|
|
103
|
+
Rails IAM can infer the required permission directly from the controller and action.
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
class UsersController < ApplicationController
|
|
107
|
+
|
|
108
|
+
authorize_resource
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The following permissions will automatically be checked:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
| Controller Action | Required Permission |
|
|
116
|
+
(UsersController) |
|
|
117
|
+
| ----------------- | ------------------- |
|
|
118
|
+
| index | user:index |
|
|
119
|
+
| show | user:show |
|
|
120
|
+
| create | user:create |
|
|
121
|
+
| update | user:update |
|
|
122
|
+
| destroy | user:destroy |
|
|
123
|
+
| send_invite | user:send_invite |
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Namespaced controllers are also supported.
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
Admin::Users::CustomersController#show -> admin_users_customer:show
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### Skip authorization
|
|
135
|
+
|
|
136
|
+
Skip authorization for an entire controller.
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
skip_authorization
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Or only for selected actions.
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
skip_authorization only: :sign_up
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
More example [here](https://github.com/salmanx/rails_iam/wiki/Authorization-Rules).
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
That's it.
|
|
153
|
+
|
|
154
|
+
👉 No policy objects.
|
|
155
|
+
|
|
156
|
+
👉 No permission checks inside your controller actions.
|
|
157
|
+
|
|
158
|
+
👉 No authorization logic inside models.
|
|
159
|
+
|
|
160
|
+
Our controllers and models stay focused on handling requests and business logic of our application, while RailsIAM takes care of deciding **who is allowed to access them**.
|
|
161
|
+
|
|
162
|
+
## So what RailsIAM brings to us?
|
|
163
|
+
|
|
164
|
+
- **Production-ready out of the box** — Install a single gem to get jwt based authentication and authorization with Role-Based Access Control (RBAC) and Permission-Based Access Control (PBAC).
|
|
165
|
+
|
|
166
|
+
- **Authorization where it belongs** — Declare access rules at the controller level, where requests enter application. Keep controllers focused on business logic and models focused on persistence.
|
|
167
|
+
|
|
168
|
+
- **No authorization logic scattered across your application** — No policy classes, no permission checks inside controller actions, and no complex conditional logic in models.
|
|
169
|
+
|
|
170
|
+
- **Permissions are data, not code** — Roles and permissions live in the database, allowing administrators to grant, revoke, or reorganize access without changing application code.
|
|
171
|
+
|
|
172
|
+
- **Change permissions without deployments** — Updating who can access an endpoint doesn't require modifying Ruby files or redeploying the application.
|
|
173
|
+
|
|
174
|
+
- **Convention over configuration** — Automatically infer permissions from controller and action names with `authorize_resource`, while still allowing explicit rules whenever needed.
|
|
175
|
+
|
|
176
|
+
- **Authentication is optional** — Use the built-in JWT authentication for API applications, or keep your existing authentication solution such as Devise. RailsIAM's authorization layer works independently and supports both API-only and traditional Rails (ERB/HTML) applications.
|
|
177
|
+
|
|
178
|
+
- **Highly configurable** — Customize model names, current user methods, JWT settings, cookies, cache, localization, error messages, and other behaviors to fit your application's conventions.
|
|
179
|
+
|
|
180
|
+
## Continue Reading
|
|
181
|
+
|
|
182
|
+
- [Getting Started](https://github.com/salmanx/rails_iam/wiki)
|
|
183
|
+
- [Authentication](https://github.com/salmanx/rails_iam/wiki/Authentication)
|
|
184
|
+
- [Authorization](https://github.com/salmanx/rails_iam/wiki/Authorization)
|
|
185
|
+
- [Authorization Rules](https://github.com/salmanx/rails_iam/wiki/Authorization-Rules)
|
|
186
|
+
- [Configuration](https://github.com/salmanx/rails_iam/wiki/Configuration)
|
|
187
|
+
- [Testing Controller and Request Authorization](https://github.com/salmanx/rails_iam/wiki/Testing-Controller-and-Request-Authorization)
|
|
188
|
+
- [With devise and traditional rails](https://github.com/salmanx/rails_iam/wiki/With-devise-and-traditional-rails)
|
|
189
|
+
|
|
190
|
+
## Contributing
|
|
191
|
+
|
|
192
|
+
Please read the [Contributing Guidelines](https://github.com/salmanx/rails_iam/blob/main/CONTRIBUTING.md).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
|
|
5
|
+
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
|
6
|
+
load "rails/tasks/engine.rake"
|
|
7
|
+
|
|
8
|
+
# load "rails/tasks/statistics.rake"
|
|
9
|
+
|
|
10
|
+
require "bundler/gem_tasks"
|
|
11
|
+
|
|
12
|
+
require "rspec/core/rake_task"
|
|
13
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
14
|
+
task default: :spec
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsIam
|
|
4
|
+
module AuthConcern
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
def extract_refresh_token_from_header
|
|
8
|
+
header_key = "HTTP_#{refresh_token_key.upcase}"
|
|
9
|
+
|
|
10
|
+
request.get_header(header_key)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def extract_refresh_token_from_cookie
|
|
14
|
+
request.cookie_jar.encrypted[refresh_token_key]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def current_refresh_token
|
|
18
|
+
return unless (refresh_token = extract_refresh_token)
|
|
19
|
+
|
|
20
|
+
digest = Digest::SHA256.hexdigest(refresh_token)
|
|
21
|
+
RailsIam::RefreshToken.find_by(token_digest: digest)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def extract_refresh_token
|
|
25
|
+
if authentication_strategies.include?(:cookie)
|
|
26
|
+
token = extract_refresh_token_from_cookie
|
|
27
|
+
return token if token.present?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
case refresh_token_transport
|
|
31
|
+
when :header
|
|
32
|
+
extract_refresh_token_from_header
|
|
33
|
+
|
|
34
|
+
when :request_body
|
|
35
|
+
params[:refresh_token]
|
|
36
|
+
|
|
37
|
+
else
|
|
38
|
+
raise RailsIam::Authentication::Exceptions::AuthenticationError, refresh_token_transport_error
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def render_rails_iam_authorization_error(exception)
|
|
43
|
+
render json: {
|
|
44
|
+
message: exception.message,
|
|
45
|
+
code: "forbidden".upcase,
|
|
46
|
+
status: 403,
|
|
47
|
+
url: request.original_fullpath,
|
|
48
|
+
}, status: :forbidden
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def render_rails_iam_authentication_error(exception)
|
|
52
|
+
render json: {
|
|
53
|
+
message: exception.message,
|
|
54
|
+
code: "unauthorized".upcase,
|
|
55
|
+
status: 401,
|
|
56
|
+
url: request.original_fullpath,
|
|
57
|
+
}, status: :unauthorized
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsIam
|
|
4
|
+
module Authentication
|
|
5
|
+
module Authenticatable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
class_attribute :skip_authentication_rules, instance_accessor: false, default: []
|
|
10
|
+
|
|
11
|
+
before_action :authenticate!
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class_methods do
|
|
15
|
+
def skip_authentication(only: nil, except: nil)
|
|
16
|
+
self.skip_authentication_rules += [
|
|
17
|
+
RailsIam::Authentication::SkipAuthenticationRule.new(
|
|
18
|
+
only: only,
|
|
19
|
+
except: except,
|
|
20
|
+
),
|
|
21
|
+
]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def current_user
|
|
26
|
+
RailsIam::Authentication::Current.user
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def authenticate!
|
|
32
|
+
return if skip_authentication?
|
|
33
|
+
|
|
34
|
+
RailsIam::Authentication::Current.user = RailsIam::Authentication::Authenticator.call(request)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def skip_authentication?
|
|
38
|
+
self.class.skip_authentication_rules.any? do |rule|
|
|
39
|
+
rule.matches?(action_name)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsIam
|
|
4
|
+
module Authorization
|
|
5
|
+
module Authorizable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
include RailsIam::AuthConcern
|
|
8
|
+
include RailsIam::ConfigLoader
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
class_attribute :authorization_rules, instance_accessor: false, default: []
|
|
12
|
+
class_attribute :skip_authorization_rules, instance_accessor: false, default: []
|
|
13
|
+
class_attribute :resource_authorization_enabled, instance_accessor: false, default: false
|
|
14
|
+
before_action :run_authorization!
|
|
15
|
+
rescue_from RailsIam::Authorization::Exceptions::AuthorizationError, with: :render_rails_iam_authorization_error
|
|
16
|
+
rescue_from RailsIam::Authentication::Exceptions::AuthenticationError, with: :render_rails_iam_authentication_error
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class_methods do
|
|
20
|
+
def authorize_resource
|
|
21
|
+
self.resource_authorization_enabled = true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def authorize(
|
|
25
|
+
roles: [],
|
|
26
|
+
permissions: [],
|
|
27
|
+
role_match: :any,
|
|
28
|
+
permission_match: :all,
|
|
29
|
+
only: nil,
|
|
30
|
+
except: nil
|
|
31
|
+
)
|
|
32
|
+
self.authorization_rules += [
|
|
33
|
+
RailsIam::Authorization::AuthorizationRule.new(
|
|
34
|
+
roles: Array(roles),
|
|
35
|
+
permissions: Array(permissions),
|
|
36
|
+
role_match: role_match,
|
|
37
|
+
permission_match: permission_match,
|
|
38
|
+
only: only,
|
|
39
|
+
except: except,
|
|
40
|
+
),
|
|
41
|
+
]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def skip_authorization(only: nil, except: nil)
|
|
45
|
+
self.skip_authorization_rules += [
|
|
46
|
+
RailsIam::Authorization::SkipAuthorizationRule.new(
|
|
47
|
+
only: only,
|
|
48
|
+
except: except,
|
|
49
|
+
),
|
|
50
|
+
]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def run_authorization!
|
|
57
|
+
return if skip_authorization?
|
|
58
|
+
|
|
59
|
+
user = current_authenticated_user
|
|
60
|
+
|
|
61
|
+
authorize_any!(authorization_rules!, user)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def authorize_any!(rules, user)
|
|
65
|
+
last_error = nil
|
|
66
|
+
|
|
67
|
+
rules.each do |rule|
|
|
68
|
+
authorize!(
|
|
69
|
+
**rule.authorizer_arguments(
|
|
70
|
+
inferred_permission: inferred_permission_for(rule),
|
|
71
|
+
),
|
|
72
|
+
user: user,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return true
|
|
76
|
+
rescue RailsIam::Authorization::Exceptions::AuthorizationError => e
|
|
77
|
+
last_error = e
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
raise(last_error || RailsIam::Authorization::Exceptions::AuthorizationError.new)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def current_authenticated_user
|
|
84
|
+
user_method = RailsIam.configuration.current_user_method
|
|
85
|
+
|
|
86
|
+
raise RailsIam::Authentication::Exceptions::AuthenticationError, token_error unless respond_to?(user_method, true)
|
|
87
|
+
|
|
88
|
+
user = public_send(user_method)
|
|
89
|
+
|
|
90
|
+
raise RailsIam::Authentication::Exceptions::AuthenticationError, token_error if user.nil?
|
|
91
|
+
|
|
92
|
+
RailsIam::Authentication::Current.user ||= user
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def authorization_rules!
|
|
96
|
+
rules = authorization_rules.select(&:valid?)
|
|
97
|
+
|
|
98
|
+
rules << inferred_authorization_rule if self.class.resource_authorization_enabled
|
|
99
|
+
|
|
100
|
+
return rules if rules.any?
|
|
101
|
+
|
|
102
|
+
raise RailsIam::Authorization::Exceptions::AuthorizationError,
|
|
103
|
+
authorization_rule_error
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def authorize!(
|
|
107
|
+
user:, required_roles: [],
|
|
108
|
+
required_permissions: [],
|
|
109
|
+
role_match: :any,
|
|
110
|
+
permission_match: :all,
|
|
111
|
+
inferred_permission: nil
|
|
112
|
+
)
|
|
113
|
+
RailsIam::Authorization::Authorizer.call(
|
|
114
|
+
user: user,
|
|
115
|
+
required_roles: required_roles,
|
|
116
|
+
required_permissions: required_permissions,
|
|
117
|
+
inferred_permission: inferred_permission,
|
|
118
|
+
role_match: role_match,
|
|
119
|
+
permission_match: permission_match,
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def authorization_rules
|
|
124
|
+
self.class.authorization_rules.select do |rule|
|
|
125
|
+
rule.matches?(action_name)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def skip_authorization?
|
|
130
|
+
excluded_controller? || skipped_action?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def skipped_action?
|
|
134
|
+
self.class.skip_authorization_rules.any? do |rule|
|
|
135
|
+
rule.matches?(action_name)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def excluded_controller?
|
|
140
|
+
(excluded_controller_patterns || []).any? do |pattern|
|
|
141
|
+
self.class.name.match?(pattern)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def inferred_authorization_rule
|
|
146
|
+
RailsIam::Authorization::AuthorizationRule.new(
|
|
147
|
+
roles: [],
|
|
148
|
+
permissions: [inferred_permission],
|
|
149
|
+
role_match: :any,
|
|
150
|
+
permission_match: :all,
|
|
151
|
+
only: nil,
|
|
152
|
+
except: nil,
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def inferred_permission
|
|
157
|
+
resource =
|
|
158
|
+
self.class.controller_path
|
|
159
|
+
.tr("/", "_")
|
|
160
|
+
.singularize
|
|
161
|
+
|
|
162
|
+
"#{resource}:#{action_name}"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def inferred_permission_for(rule)
|
|
166
|
+
rule.permissions.any? ? nil : inferred_permission
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|