verikloak-pundit 0.2.2 → 0.2.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 40f6167b3a558b93b98bcabbe529acc50b4ffef682d9fc02375ea85c8f9ce26f
4
- data.tar.gz: 31ca32b4215cc022474e5f385a56f3c6b189137665cdb76b5f748842c12cdce2
3
+ metadata.gz: 7a29b74665fc8bf023f1b6aeea48575b55227a5a4c0d8deae339b02651885814
4
+ data.tar.gz: fda84a30fa38d00819b5b00c4d72a2a8858355c4faaba2dcef8ec1a956df7753
5
5
  SHA512:
6
- metadata.gz: e7c270e29f9872f007ebd3863946cb665bc4d62973cfcf7e2f635533ae1e20eaba64511f474e7a33bb3dd6040a751fca2b25027db4457690bdafd9333c05cac1
7
- data.tar.gz: 81887295612920fe124f1947e1ecc9a91143beda4097ece57015739bb1e28481d5027a2d75e1e77545f4e98c7b5c4cd5c2fab43e86c5a8b386c965dfbe0696ac
6
+ metadata.gz: 928b8e33c892000b074a6c8fe12aece68a14140d5994b9f13640d074b39130f560a49e3fabc1a07917e0678d89e6f8a1b797a73dcb3f579b5ffeef622398050d
7
+ data.tar.gz: 7c418267c589a0a13a0aafdbb2cc48c4fa4cefb87a61d8051a3257e3250790b7be48ac79b7e3fc96421c4ad1a966d0ba511277d67e872d7e7c6f5e6fced09412
data/CHANGELOG.md CHANGED
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.2.4] - 2026-01-01
11
+
12
+ ### Added
13
+ - **Environment variable fallback**: `resource_client` now falls back to `ENV['KEYCLOAK_RESOURCE_CLIENT']` when not explicitly configured, enabling environment-based configuration.
14
+ - **Auto-sync with verikloak-rails**: When used alongside `verikloak-rails`, `env_claims_key` is automatically synchronized from `Verikloak::Rails.config.user_env_key` if not explicitly set.
15
+
16
+ ### Changed
17
+ - **Simplified initializer template**: Generator now produces a minimal initializer with commented examples instead of explicit defaults. Most settings work out of the box.
18
+ - **README improvements**: Updated Configuration section with environment variables table, auto-configuration documentation, and removed redundant examples.
19
+ - **Consistent Pundit include**: Quick Start examples now use `Pundit::Authorization` consistently.
20
+
21
+ ## [0.2.3] - 2025-12-31
22
+
23
+ ### Added
24
+ - **Database User Integration Guide**: New README section documenting the custom `UserContext` pattern for combining JWT claims with database user models, including controller setup and policy examples.
25
+ - **Delegations Module Documentation**: Comprehensive usage guide for `Verikloak::Pundit::Delegations`, covering requirements, custom `UserContext` compatibility, and nil user handling patterns.
26
+
27
+ ### Changed
28
+ - Clarified controller setup examples to use `Verikloak::Pundit::Controller` (this gem) instead of `Verikloak::Rails::Controller` (verikloak-rails gem).
29
+ - Added explicit notes about method origins (`verikloak_claims` vs `current_user_claims`) for users combining multiple Verikloak gems.
30
+ - Enhanced nil user handling documentation with `safe_has_role?` helper pattern for public endpoints.
31
+ - Bump minimum `verikloak` dependency from `>= 0.2.0` to `>= 0.3.0` to align with latest upstream releases.
32
+ - Update Ruby version to 3.4.8 in development environment.
33
+
10
34
  ## [0.2.2] - 2025-09-28
11
35
 
12
36
  ### Changed
data/README.md CHANGED
@@ -43,7 +43,7 @@ For error-handling guidance, see [ERRORS.md](ERRORS.md).
43
43
  ```ruby
44
44
  # app/controllers/application_controller.rb
45
45
  class ApplicationController < ActionController::API
46
- include Pundit
46
+ include Pundit::Authorization
47
47
  include Verikloak::Pundit::Controller # provides pundit_user
48
48
 
49
49
  # If you're also using verikloak-rails:
@@ -65,38 +65,28 @@ Where `user` is the **UserContext** provided by `pundit_user`.
65
65
 
66
66
  ## Configuration
67
67
 
68
+ Most settings have sensible defaults and can be auto-configured. You only need to customize what's different for your application.
69
+
70
+ ### Environment Variables
71
+
72
+ | Variable | Description | Default |
73
+ |----------|-------------|---------|
74
+ | `KEYCLOAK_RESOURCE_CLIENT` | Default resource client ID for resource roles | `"rails-api"` |
75
+
76
+ ### Auto-configuration with verikloak-rails
77
+
78
+ When used alongside `verikloak-rails`, the following settings are automatically synchronized:
79
+
80
+ - **`env_claims_key`**: Inherits from `Verikloak::Rails.config.user_env_key` if you haven't explicitly set it. This ensures both gems read claims from the same Rack env key.
81
+
82
+ This means in most cases you can use a minimal initializer:
83
+
68
84
  ```ruby
69
- # config/initializers/verikloak_pundit.rb
70
85
  Verikloak::Pundit.configure do |c|
71
- c.resource_client = "rails-api" # default client for resource roles
72
- c.role_map = { # optional role → permission mapping
86
+ c.role_map = {
73
87
  admin: :manage_all,
74
- editor: :write_notes,
75
- reader: :read_notes
88
+ editor: :write_notes
76
89
  }
77
- # Where to find claims in Rack env (when using verikloak/verikloak-rails)
78
- c.env_claims_key = "verikloak.user"
79
-
80
- # How to traverse JWT for roles
81
- c.realm_roles_path = %w[realm_access roles] # => claims["realm_access"]["roles"]
82
- # Lambdas in the path may accept (cfg) or (cfg, client)
83
- # where `client` is the argument passed to `user.resource_roles(client)`
84
- c.resource_roles_path = ["resource_access", ->(cfg){ cfg.resource_client }, "roles"]
85
-
86
- # Permission mapping scope for `user.has_permission?`:
87
- # :default_resource => realm roles + default client roles (recommended)
88
- # :all_resources => realm roles + roles from all clients in resource_access
89
- # (enabling this broadens permissions to every resource client;
90
- # review the upstream role assignments before turning it on)
91
- c.permission_role_scope = :default_resource
92
- # Optional whitelist of resource clients when `permission_role_scope = :all_resources`.
93
- # Leaving this as nil keeps the legacy "all clients" behavior, while providing
94
- # an explicit list (e.g., %w[rails-api verikloak-bff]) limits which clients can
95
- # contribute roles to permission checks.
96
- c.permission_resource_clients = nil
97
-
98
- # Expose `verikloak_claims` to views via helper_method (Rails only)
99
- c.expose_helper_method = true
100
90
  end
101
91
  ```
102
92
 
@@ -105,12 +95,11 @@ end
105
95
  - **verikloak-bff**: When your Rails application sits behind the BFF, the access
106
96
  token presented to verikloak-pundit typically originates from the BFF
107
97
  (e.g. via the `x-verikloak-user` header). Make sure your Rack stack stores the
108
- decoded claims under the same `env_claims_key` configured above (the default
109
- `"verikloak.user"` works out of the box with `verikloak-bff >= 0.3`). If the
110
- BFF issues tokens for multiple downstream services, set
111
- `permission_resource_clients` to the limited list of clients whose roles should
112
- affect Rails-side authorization to avoid accidentally inheriting permissions
113
- meant for other services.
98
+ decoded claims under the same `env_claims_key` (default: `"verikloak.user"`,
99
+ which works out of the box with `verikloak-bff >= 0.3`). If the BFF issues
100
+ tokens for multiple downstream services, set `permission_resource_clients` to
101
+ the limited list of clients whose roles should affect Rails-side authorization
102
+ to avoid accidentally inheriting permissions meant for other services.
114
103
  - **verikloak-audience**: Audience services often mint resource roles with a
115
104
  service-specific prefix (for example, `audience-service:editor`). Align your
116
105
  `role_map` keys with that naming convention so `user.has_permission?` resolves
@@ -118,6 +107,177 @@ end
118
107
  that client id to `permission_resource_clients` when you need to consume those
119
108
  roles from Rails.
120
109
 
110
+ ## Integrating with Database User Models
111
+
112
+ ### Overview
113
+
114
+ `Verikloak::Pundit::UserContext` wraps JWT claims for use in Pundit policies. However, real applications often need to access database User models for additional attributes (e.g., `user.admin?`, `user.organization_id`).
115
+
116
+ ### Custom UserContext Pattern
117
+
118
+ Create a custom UserContext that holds both JWT claims and a database user reference:
119
+
120
+ ```ruby
121
+ # app/lib/app_user_context.rb
122
+ class AppUserContext < Verikloak::Pundit::UserContext
123
+ attr_reader :db_user
124
+
125
+ def initialize(claims, db_user: nil, resource_client: nil, config: nil)
126
+ super(claims, resource_client: resource_client, config: config)
127
+ @db_user = db_user
128
+ end
129
+
130
+ # Delegate database user methods
131
+ delegate :admin?, :organization_id, :active?, to: :db_user, allow_nil: true
132
+
133
+ # Custom authorization helpers
134
+ def owns?(record)
135
+ return false unless db_user && record
136
+ record.respond_to?(:user_id) && db_user.id == record.user_id
137
+ end
138
+
139
+ def same_organization?(record)
140
+ return false unless db_user && record
141
+ record.respond_to?(:organization_id) && db_user.organization_id == record.organization_id
142
+ end
143
+ end
144
+ ```
145
+
146
+ ### Controller Setup
147
+
148
+ Override `pundit_user` in your ApplicationController. This example assumes you are using `verikloak-rails`, which provides `current_user_claims` and related helpers:
149
+
150
+ ```ruby
151
+ # app/controllers/application_controller.rb
152
+ class ApplicationController < ActionController::API
153
+ include Pundit::Authorization
154
+ include Verikloak::Pundit::Controller # Provides pundit_user and verikloak_claims
155
+
156
+ # If using verikloak-rails for JWT verification:
157
+ # include Verikloak::Rails::Controller # Provides current_user_claims, current_subject, etc.
158
+
159
+ def pundit_user
160
+ @pundit_user ||= AppUserContext.new(
161
+ verikloak_claims, # From Verikloak::Pundit::Controller
162
+ db_user: find_or_create_current_user,
163
+ config: Verikloak::Pundit.config
164
+ )
165
+ end
166
+
167
+ private
168
+
169
+ def find_or_create_current_user
170
+ # Extract subject from claims
171
+ sub = verikloak_claims&.dig('sub')
172
+ return nil unless sub
173
+
174
+ User.find_or_create_by(sub: sub) do |user|
175
+ user.email = verikloak_claims&.dig('email')
176
+ user.name = verikloak_claims&.dig('name')
177
+ end
178
+ end
179
+ end
180
+ ```
181
+
182
+ > **Note:** If you are also using `verikloak-rails`, you can use its `current_user_claims` method instead of `verikloak_claims`. Both provide access to the JWT claims from the Rack environment.
183
+
184
+ ### Policy Example
185
+
186
+ Now your policies can use both JWT claims and database attributes:
187
+
188
+ ```ruby
189
+ # app/policies/document_policy.rb
190
+ class DocumentPolicy < ApplicationPolicy
191
+ def show?
192
+ # Combine JWT roles with database attributes
193
+ user.has_role?(:admin) || user.owns?(record) || user.same_organization?(record)
194
+ end
195
+
196
+ def update?
197
+ user.admin? || user.owns?(record) # Uses delegated db_user.admin?
198
+ end
199
+
200
+ def destroy?
201
+ user.has_role?(:admin) && user.active? # JWT role + DB attribute
202
+ end
203
+ end
204
+ ```
205
+
206
+ ## Delegations Module
207
+
208
+ ### Overview
209
+
210
+ `Verikloak::Pundit::Delegations` provides shortcut methods for role and permission checks in policies.
211
+
212
+ ### Usage
213
+
214
+ Include in your ApplicationPolicy to access helpers directly:
215
+
216
+ ```ruby
217
+ class ApplicationPolicy
218
+ include Verikloak::Pundit::Delegations
219
+
220
+ # Now you can use:
221
+ # - has_role?(:admin) instead of user.has_role?(:admin)
222
+ # - in_group?(:editors) instead of user.in_group?(:editors)
223
+ # - resource_role?(client, role)
224
+ # - has_permission?(:manage_all)
225
+ end
226
+ ```
227
+
228
+ ### Requirements
229
+
230
+ - The policy must have a `user` method that returns a `Verikloak::Pundit::UserContext` (or subclass)
231
+ - If `user` is `nil`, delegation methods will raise `NoMethodError`
232
+
233
+ ### Compatibility with Custom UserContext
234
+
235
+ Delegations work with any class that inherits from `Verikloak::Pundit::UserContext`:
236
+
237
+ ```ruby
238
+ # Works with AppUserContext (shown above)
239
+ class DocumentPolicy < ApplicationPolicy
240
+ include Verikloak::Pundit::Delegations
241
+
242
+ def update?
243
+ has_role?(:admin) || has_permission?(:write_documents)
244
+ end
245
+ end
246
+ ```
247
+
248
+ ### Handling nil user
249
+
250
+ For policies that may receive `nil` users (e.g., public endpoints), you **must** guard against nil before calling delegation methods:
251
+
252
+ ```ruby
253
+ class PublicDocumentPolicy < ApplicationPolicy
254
+ def show?
255
+ return true if record.public?
256
+ return false unless user # Guard against nil user before using delegations
257
+
258
+ has_role?(:viewer)
259
+ end
260
+ end
261
+ ```
262
+
263
+ Alternatively, create a helper method in your `ApplicationPolicy`:
264
+
265
+ ```ruby
266
+ class ApplicationPolicy
267
+ include Verikloak::Pundit::Delegations
268
+
269
+ private
270
+
271
+ def authenticated?
272
+ !user.nil?
273
+ end
274
+
275
+ def safe_has_role?(role)
276
+ authenticated? && has_role?(role)
277
+ end
278
+ end
279
+ ```
280
+
121
281
  ## Non-Rails / custom usage
122
282
 
123
283
  ```ruby
@@ -148,35 +308,16 @@ RSpec.configure do |config|
148
308
  end
149
309
  ```
150
310
 
151
- An additional integration check exercises the gem together with the latest `verikloak` and `verikloak-rails` releases. This runs in CI automatically, and you can execute it locally with:
152
-
153
- ```bash
154
- docker compose run --rm -e BUNDLE_FROZEN=0 dev bash -lc '
155
- cd integration && \
156
- apk add --no-cache --virtual .integration-build-deps \
157
- build-base \
158
- linux-headers \
159
- openssl-dev \
160
- yaml-dev && \
161
- bundle config set --local path vendor/bundle && \
162
- bundle install --jobs 4 --retry 3 && \
163
- bundle exec ruby check.rb && \
164
- apk del .integration-build-deps
165
- '
166
- ```
167
-
168
311
  ## Contributing
169
312
  Bug reports and pull requests are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
170
313
 
171
314
  ## Security
172
315
  If you find a security vulnerability, please follow the instructions in [SECURITY.md](SECURITY.md).
173
316
 
174
- ### Operational guidance
317
+ ## Operational Guidance
318
+
175
319
  - Enabling `permission_role_scope = :all_resources` pulls roles from every Keycloak client in `resource_access`. Review the granted roles carefully to ensure you are not expanding permissions beyond what the application expects.
176
- - Combine `permission_role_scope = :all_resources` with `permission_resource_clients`
177
- to explicitly opt-in the clients that may contribute permissions. Leaving the
178
- whitelist blank (the default) reverts to the legacy behavior of trusting
179
- every client in the token.
320
+ - Combine `permission_role_scope = :all_resources` with `permission_resource_clients` to explicitly opt-in the clients that may contribute permissions. Leaving the whitelist blank (the default) reverts to the legacy behavior of trusting every client in the token.
180
321
  - Leaving `expose_helper_method = true` exposes `verikloak_claims` to the Rails view layer. If the claims include personal or sensitive data, consider switching it to `false` and pass only the minimum required information through controller-provided helpers.
181
322
 
182
323
  ## License
@@ -1,24 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Verikloak::Pundit initializer
4
- # Configure how to read roles from Keycloak claims and how to map them
5
- # into application permissions.
4
+ #
5
+ # Most settings are auto-configured from environment variables and verikloak-rails.
6
+ # Uncomment and customize only what you need to override.
7
+ #
8
+ # Environment variables supported:
9
+ # KEYCLOAK_RESOURCE_CLIENT - resource client ID (default: 'rails-api')
10
+ #
11
+ # When used with verikloak-rails, env_claims_key is automatically synchronized.
6
12
  Verikloak::Pundit.configure do |c|
7
- c.resource_client = ENV.fetch('KEYCLOAK_RESOURCE_CLIENT', 'rails-api')
8
- c.role_map = {
9
- # admin: :manage_all,
10
- # editor: :write_notes,
11
- # reader: :read_notes
12
- }
13
- c.env_claims_key = 'verikloak.user'
14
- # claims['realm_access']['roles']
15
- c.realm_roles_path = %w[realm_access roles]
16
- # rubocop:disable Style/SymbolProc -- we need a Proc object here, not block pass
17
- # claims['resource_access'][resource_client]['roles']
18
- c.resource_roles_path = ['resource_access', ->(cfg) { cfg.resource_client }, 'roles']
19
- # rubocop:enable Style/SymbolProc
20
- # Permission mapping scope:
21
- # :default_resource => realm roles + default client roles (recommended)
22
- # :all_resources => realm roles + roles from all clients in resource_access
23
- c.permission_role_scope = :default_resource
13
+ # Resource client (optional - falls back to ENV['KEYCLOAK_RESOURCE_CLIENT'] or 'rails-api')
14
+ # c.resource_client = ENV.fetch('KEYCLOAK_RESOURCE_CLIENT', 'rails-api')
15
+
16
+ # Role to permission mapping (optional)
17
+ # c.role_map = {
18
+ # admin: :manage_all,
19
+ # editor: :write_notes,
20
+ # reader: :read_notes
21
+ # }
22
+
23
+ # Uncomment to customize JWT claims path and scope (usually not needed):
24
+ # c.env_claims_key = 'verikloak.user'
25
+ # c.realm_roles_path = %w[realm_access roles]
26
+ # c.resource_roles_path = ['resource_access', ->(cfg) { cfg.resource_client }, 'roles']
27
+ # c.permission_role_scope = :default_resource # or :all_resources
24
28
  end
@@ -22,11 +22,20 @@ module Verikloak
22
22
  # @!attribute expose_helper_method
23
23
  # @return [Boolean] whether to register `verikloak_claims` as a Rails helper method
24
24
  class Configuration
25
- attr_accessor :resource_client, :role_map, :env_claims_key,
25
+ attr_accessor :role_map, :env_claims_key,
26
26
  :realm_roles_path, :resource_roles_path,
27
27
  :permission_role_scope, :permission_resource_clients,
28
28
  :expose_helper_method
29
29
 
30
+ attr_writer :resource_client
31
+
32
+ # Returns the resource client, falling back to ENV['KEYCLOAK_RESOURCE_CLIENT'] if not set.
33
+ #
34
+ # @return [String]
35
+ def resource_client
36
+ @resource_client || ENV.fetch('KEYCLOAK_RESOURCE_CLIENT', 'rails-api')
37
+ end
38
+
30
39
  # Build a new configuration, optionally copying values from another
31
40
  # configuration so callers can mutate a safe duplicate.
32
41
  #
@@ -77,7 +86,7 @@ module Verikloak
77
86
 
78
87
  # Populate default values that mirror the gem's out-of-the-box behavior.
79
88
  def initialize_defaults
80
- @resource_client = 'rails-api'
89
+ @resource_client = nil # Falls back to ENV['KEYCLOAK_RESOURCE_CLIENT'] or 'rails-api'
81
90
  @role_map = {} # e.g., { admin: :manage_all }
82
91
  @env_claims_key = 'verikloak.user'
83
92
  @realm_roles_path = %w[realm_access roles]
@@ -12,6 +12,35 @@ module Verikloak
12
12
  include Verikloak::Pundit::Controller
13
13
  end
14
14
  end
15
+
16
+ # Synchronize configuration with verikloak-rails when available.
17
+ # Runs after verikloak-rails configuration is applied.
18
+ initializer 'verikloak_pundit.sync_configuration', after: 'verikloak.configure' do
19
+ sync_with_verikloak_rails if defined?(Verikloak::Rails)
20
+ end
21
+
22
+ class << self
23
+ # Sync env_claims_key with verikloak-rails configuration.
24
+ # Only applies if env_claims_key has not been explicitly set.
25
+ #
26
+ # @return [void]
27
+ def sync_with_verikloak_rails
28
+ return unless Verikloak::Rails.respond_to?(:config)
29
+
30
+ rails_config = Verikloak::Rails.config
31
+ return unless rails_config.respond_to?(:user_env_key) && rails_config.user_env_key
32
+
33
+ current_key = Verikloak::Pundit.config.env_claims_key
34
+ return unless current_key == 'verikloak.user'
35
+
36
+ # Use configure to properly update frozen config
37
+ Verikloak::Pundit.configure do |c|
38
+ c.env_claims_key = rails_config.user_env_key
39
+ end
40
+ rescue StandardError => e
41
+ warn "[verikloak-pundit] Failed to sync with verikloak-rails: #{e.message}"
42
+ end
43
+ end
15
44
  end
16
45
  end
17
46
  end
@@ -5,6 +5,6 @@ module Verikloak
5
5
  # Gem version for verikloak-pundit.
6
6
  #
7
7
  # @return [String]
8
- VERSION = '0.2.2'
8
+ VERSION = '0.2.4'
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: verikloak-pundit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - taiyaky
@@ -49,7 +49,7 @@ dependencies:
49
49
  requirements:
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
- version: 0.2.0
52
+ version: 0.3.0
53
53
  - - "<"
54
54
  - !ruby/object:Gem::Version
55
55
  version: 1.0.0
@@ -59,7 +59,7 @@ dependencies:
59
59
  requirements:
60
60
  - - ">="
61
61
  - !ruby/object:Gem::Version
62
- version: 0.2.0
62
+ version: 0.3.0
63
63
  - - "<"
64
64
  - !ruby/object:Gem::Version
65
65
  version: 1.0.0
@@ -94,7 +94,7 @@ metadata:
94
94
  source_code_uri: https://github.com/taiyaky/verikloak-pundit
95
95
  changelog_uri: https://github.com/taiyaky/verikloak-pundit/blob/main/CHANGELOG.md
96
96
  bug_tracker_uri: https://github.com/taiyaky/verikloak-pundit/issues
97
- documentation_uri: https://rubydoc.info/gems/verikloak-pundit/0.2.2
97
+ documentation_uri: https://rubydoc.info/gems/verikloak-pundit/0.2.4
98
98
  rubygems_mfa_required: 'true'
99
99
  rdoc_options: []
100
100
  require_paths: