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.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/CONTRIBUTING.md +145 -0
  4. data/LICENSE +20 -0
  5. data/README.md +192 -0
  6. data/Rakefile +14 -0
  7. data/app/controllers/concerns/rails_iam/auth_concern.rb +60 -0
  8. data/app/controllers/concerns/rails_iam/authentication/authenticatable.rb +44 -0
  9. data/app/controllers/concerns/rails_iam/authorization/authorizable.rb +170 -0
  10. data/app/controllers/rails_iam/application_controller.rb +7 -0
  11. data/app/controllers/rails_iam/auth_controller.rb +113 -0
  12. data/app/helpers/rails_iam/config_loader.rb +71 -0
  13. data/app/models/concerns/rails_iam/auditable.rb +57 -0
  14. data/app/models/concerns/rails_iam/user_concern.rb +59 -0
  15. data/app/models/rails_iam/application_record.rb +8 -0
  16. data/app/models/rails_iam/permission.rb +14 -0
  17. data/app/models/rails_iam/refresh_token.rb +7 -0
  18. data/app/models/rails_iam/role.rb +14 -0
  19. data/app/models/rails_iam/role_permission.rb +20 -0
  20. data/app/models/rails_iam/user.rb +16 -0
  21. data/app/models/rails_iam/user_denied_permission.rb +12 -0
  22. data/app/models/rails_iam/user_permission.rb +12 -0
  23. data/app/models/rails_iam/user_role.rb +12 -0
  24. data/app/services/application_service.rb +4 -0
  25. data/app/services/rails_iam/authentication/authenticated_user.rb +13 -0
  26. data/app/services/rails_iam/authentication/authenticator.rb +39 -0
  27. data/app/services/rails_iam/authentication/current.rb +9 -0
  28. data/app/services/rails_iam/authentication/exceptions/authentication_error.rb +10 -0
  29. data/app/services/rails_iam/authentication/jwt_decoder.rb +38 -0
  30. data/app/services/rails_iam/authentication/jwt_encoder.rb +45 -0
  31. data/app/services/rails_iam/authentication/login.rb +67 -0
  32. data/app/services/rails_iam/authentication/logout.rb +35 -0
  33. data/app/services/rails_iam/authentication/refresh_token_encoder.rb +11 -0
  34. data/app/services/rails_iam/authentication/skip_authentication_rule.rb +20 -0
  35. data/app/services/rails_iam/authentication/token_extractor.rb +64 -0
  36. data/app/services/rails_iam/authorization/authorization_context.rb +41 -0
  37. data/app/services/rails_iam/authorization/authorization_rule.rb +38 -0
  38. data/app/services/rails_iam/authorization/authorizer.rb +35 -0
  39. data/app/services/rails_iam/authorization/cache.rb +39 -0
  40. data/app/services/rails_iam/authorization/denied_permission_checker.rb +33 -0
  41. data/app/services/rails_iam/authorization/exceptions/authorization_error.rb +10 -0
  42. data/app/services/rails_iam/authorization/exceptions/missing_permission_error.rb +35 -0
  43. data/app/services/rails_iam/authorization/exceptions/missing_role_error.rb +35 -0
  44. data/app/services/rails_iam/authorization/permission_checker.rb +42 -0
  45. data/app/services/rails_iam/authorization/permission_resolver.rb +70 -0
  46. data/app/services/rails_iam/authorization/permission_sources/role_permission_source.rb +37 -0
  47. data/app/services/rails_iam/authorization/permission_sources/user_denied_permission_source.rb +29 -0
  48. data/app/services/rails_iam/authorization/permission_sources/user_permission_source.rb +28 -0
  49. data/app/services/rails_iam/authorization/resolved_permissions.rb +63 -0
  50. data/app/services/rails_iam/authorization/role_checker.rb +35 -0
  51. data/app/services/rails_iam/authorization/role_resolver.rb +30 -0
  52. data/app/services/rails_iam/authorization/skip_authorization_rule.rb +19 -0
  53. data/app/services/rails_iam/rails_iam_service.rb +11 -0
  54. data/config/routes.rb +25 -0
  55. data/lib/generators/rails_iam/install_generator.rb +49 -0
  56. data/lib/generators/rails_iam/templates/create_rails_iam_tables.rb.erb +119 -0
  57. data/lib/generators/rails_iam/templates/initializer.rb +162 -0
  58. data/lib/rails_iam/config/authentication.rb +65 -0
  59. data/lib/rails_iam/config/authorization.rb +36 -0
  60. data/lib/rails_iam/config/jwt.rb +27 -0
  61. data/lib/rails_iam/configuration.rb +30 -0
  62. data/lib/rails_iam/controller_dsl.rb +41 -0
  63. data/lib/rails_iam/engine.rb +34 -0
  64. data/lib/rails_iam/model_dsl.rb +13 -0
  65. data/lib/rails_iam/version.rb +5 -0
  66. data/lib/rails_iam.rb +27 -0
  67. data/lib/tasks/rails_iam_tasks.rake +6 -0
  68. data/rails_iam.gemspec +46 -0
  69. 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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsIam
4
+ # or ActionController::Base
5
+ class ApplicationController < ActionController::API
6
+ end
7
+ end