verikloak-rails 1.0.1 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dbb1d62264480e1ed9e4f045df4d1cd694e1ed7fdc2240c815d98ecd5dd39b33
4
- data.tar.gz: 94bbb11fadae1cb555be97cc4715bdf357cf9252464da6b303bf4fb98ea3d77c
3
+ metadata.gz: 3b4d8e46a9c366b09726841773f09cbf54b44c3fbf2f0870b1994bb365ae9173
4
+ data.tar.gz: 31f3fdf2f6c9482f06a1a03bf0a4ecdc8109f24c814306e174ff582706659c4f
5
5
  SHA512:
6
- metadata.gz: 62c094a2c975973123e36f50aba4ccbbb2fc58fb82de69f3ad8b40a2363051fc02b1119fbb393854a3bb7faba30b2618035340aee1bf8cc95620ce0e4cdbe5c1
7
- data.tar.gz: 24626f36ab835977055470020cc9371f7d76217a59f4907010a9f2d7761ece802a0ea4f3a57497306e543ffa369ad9ee95f32a8c41c8f027b25ad8c871e0c33e
6
+ metadata.gz: 6748be8631a59e8d1f0562c96ae6b53b01991d28a5fd094cf0117164d7597508c0d02e8be5ebdc28ad04c801686dac0492eb6f5242ea09e961a1814d4b0ff248
7
+ data.tar.gz: a41eb76616575e283c7ff139161fa18886b85ffea7f7d953eb4ed846b754fb1e15a93538d3f8ed51b04d92e68f302b3174d54ba881e600b2c58d9f8979687c6b
data/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.1.0] - 2026-05-09
11
+
12
+ ### Added
13
+ - **RSpec testing helpers** (`Verikloak::Rails::Testing`): a reusable test-support layer so applications no longer have to hand-roll their own Verikloak stubs/claim builders. Composed of three independent modules — `ClaimsBuilder` (build OIDC-shaped claim Hashes from a user-like object), `MiddlewareStub` (stub `Verikloak::Middleware` and, when loaded, `Verikloak::BFF::HeaderGuard` / `Verikloak::Audience::Middleware` to inject claims into `env['verikloak.user']`), and `Helpers` (top-level mix-in plus `Verikloak::Pundit::UserContext` builders when `verikloak-pundit` is loaded). Require `verikloak/rails/testing/rspec` from your `rails_helper.rb` to mix the helpers into request and policy specs and to register the `with verikloak admin auth`, `with verikloak user auth`, and `with verikloak custom auth` shared contexts. Note: `MiddlewareStub` requires RSpec, and controller specs (`type: :controller`) bypass the Rack middleware stack and are therefore not auto-included; set `request.env['verikloak.user']` directly in those specs. See README "Testing Support" for usage.
14
+
15
+ ### Changed
16
+ - **`ClaimsBuilder#build_jwt_claims`** falls back to the user's email when `username` / `preferred_username` is present but blank, preventing the `preferred_username` claim from being silently dropped by `.compact`.
17
+
18
+ ### Security
19
+ - **Bumped `rails` to `>= 8.1.3` and `json` to `>= 2.19.5`** in `Gemfile.lock` to clear known advisories surfaced by `bundler-audit` (Active Storage range-header DoS and json format-string injection).
20
+
21
+ ---
22
+
10
23
  ## [1.0.1] - 2026-03-08
11
24
 
12
25
  ### Fixed
data/README.md CHANGED
@@ -224,6 +224,95 @@ end
224
224
  | `rescue_pundit` | `VERIKLOAK_RESCUE_PUNDIT` |
225
225
 
226
226
 
227
+ ## Testing Support
228
+
229
+ `verikloak-rails` ships RSpec helpers so applications no longer need to
230
+ hand-roll their own Verikloak stubs and claim builders.
231
+
232
+ ### Setup
233
+
234
+ Require the integration once from `spec/rails_helper.rb` (or
235
+ `spec/spec_helper.rb`):
236
+
237
+ ```ruby
238
+ require "verikloak/rails/testing/rspec"
239
+ ```
240
+
241
+ This mixes `Verikloak::Rails::Testing::Helpers` into request and policy
242
+ specs, and registers three shared contexts:
243
+
244
+ - `"with verikloak admin auth"` — authenticates as an admin (`groups: ["/admin"]`)
245
+ - `"with verikloak user auth"` — authenticates as a regular user (`groups: ["/user"]`)
246
+ - `"with verikloak custom auth"` — uses `let(:verikloak_groups)` /
247
+ `let(:verikloak_extra_claims)` to customise the injected claims
248
+
249
+ > Controller specs (`type: :controller`) bypass the Rack middleware
250
+ > stack, so `stub_verikloak_middleware` cannot inject claims through
251
+ > them. Prefer `type: :request`; if you must use a controller spec,
252
+ > set `request.env['verikloak.user']`/`request.env['verikloak.token']`
253
+ > directly in a `before` block.
254
+
255
+ The shared contexts assume a `current_user` factory exists (e.g.
256
+ `create(:user)`); override `let(:current_user)` to inject a different
257
+ object. The user object is duck-typed and only needs to respond to `uid`
258
+ and `email` (with optional `username` / `preferred_username` /
259
+ `first_name` / `last_name`).
260
+
261
+ ### Request specs
262
+
263
+ ```ruby
264
+ RSpec.describe "Users API", type: :request do
265
+ include_context "with verikloak admin auth"
266
+
267
+ it "returns the users list" do
268
+ get "/api/v1/users"
269
+ expect(response).to have_http_status(:ok)
270
+ end
271
+ end
272
+ ```
273
+
274
+ Or call the helpers directly when you need a custom claim shape:
275
+
276
+ ```ruby
277
+ RSpec.describe "Custom claims", type: :request do
278
+ let(:user) { create(:user) }
279
+
280
+ before do
281
+ claims = build_jwt_claims(
282
+ user,
283
+ groups: ["/custom-group"],
284
+ extra_claims: { "custom" => "value" }
285
+ )
286
+ stub_verikloak_middleware(claims)
287
+ end
288
+ end
289
+ ```
290
+
291
+ `stub_verikloak_middleware` automatically also stubs
292
+ `Verikloak::BFF::HeaderGuard` and `Verikloak::Audience::Middleware` when
293
+ those gems are loaded, and sets `env['verikloak.token']` so controller
294
+ helpers like `current_token` work.
295
+
296
+ ### Policy specs (with `verikloak-pundit`)
297
+
298
+ When `verikloak-pundit` is loaded, `Verikloak::Pundit::UserContext`
299
+ builders become available:
300
+
301
+ ```ruby
302
+ RSpec.describe UserPolicy, type: :policy do
303
+ let(:user) { create(:user) }
304
+ let(:admin_context) { build_admin_user_context(user) }
305
+ let(:user_context) { build_user_user_context(user) }
306
+
307
+ it "allows admin to index" do
308
+ expect(described_class.new(admin_context, User).index?).to be true
309
+ end
310
+ end
311
+ ```
312
+
313
+ If `verikloak-pundit` is not loaded, calling the `*_user_context`
314
+ builders raises a clear error.
315
+
227
316
  ## Errors
228
317
  This gem standardizes JSON error responses and HTTP statuses. See [ERRORS.md](ERRORS.md) for details and examples.
229
318
 
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Verikloak
4
+ module Rails
5
+ module Testing
6
+ # Builds JWT-shaped claim Hashes from a user-like object for use in
7
+ # tests. The returned Hash uses string keys to match what
8
+ # `Verikloak::Middleware` writes to `env['verikloak.user']` after a
9
+ # successful token verification.
10
+ #
11
+ # The user object is duck-typed: it must respond to `uid` and `email`.
12
+ # Optional methods used when present:
13
+ # - `username` or `preferred_username` (for `preferred_username`)
14
+ # - `first_name` (for `given_name`)
15
+ # - `last_name` (for `family_name`)
16
+ module ClaimsBuilder
17
+ # Build a baseline OIDC-style claim Hash.
18
+ #
19
+ # @param user [Object] user-like object responding to `uid`, `email`
20
+ # @param groups [Array<String>] values for the `groups` claim
21
+ # @param extra_claims [Hash] additional claims to merge in (overrides
22
+ # any keys produced from `user`/`groups`)
23
+ # @return [Hash{String=>Object}]
24
+ def build_jwt_claims(user, groups: [], extra_claims: {})
25
+ base = {
26
+ 'sub' => user.uid,
27
+ 'email' => user.email,
28
+ 'preferred_username' => preferred_username_for(user),
29
+ 'given_name' => safe_call(user, :first_name),
30
+ 'family_name' => safe_call(user, :last_name),
31
+ 'groups' => groups,
32
+ 'realm_access' => { 'roles' => [] },
33
+ 'resource_access' => {},
34
+ 'aud' => ['account']
35
+ }.compact
36
+
37
+ base.merge(stringify_keys(extra_claims))
38
+ end
39
+
40
+ # Convenience wrapper that assigns the configured admin group.
41
+ #
42
+ # @param user [Object]
43
+ # @param admin_group [String] group identifier (default: "/admin")
44
+ # @param extra_claims [Hash]
45
+ # @return [Hash{String=>Object}]
46
+ def build_admin_claims(user, admin_group: '/admin', extra_claims: {})
47
+ build_jwt_claims(user, groups: [admin_group], extra_claims: extra_claims)
48
+ end
49
+
50
+ # Convenience wrapper that assigns the configured user group.
51
+ #
52
+ # @param user [Object]
53
+ # @param user_group [String] group identifier (default: "/user")
54
+ # @param extra_claims [Hash]
55
+ # @return [Hash{String=>Object}]
56
+ def build_user_claims(user, user_group: '/user', extra_claims: {})
57
+ build_jwt_claims(user, groups: [user_group], extra_claims: extra_claims)
58
+ end
59
+
60
+ private
61
+
62
+ def preferred_username_for(user)
63
+ candidate = if user.respond_to?(:username)
64
+ user.username
65
+ elsif user.respond_to?(:preferred_username)
66
+ user.preferred_username
67
+ end
68
+
69
+ # Fall back to email when the configured method exists but
70
+ # returns a blank value (nil/empty), so the
71
+ # `preferred_username` claim is always populated when the user
72
+ # has at least an email address.
73
+ present?(candidate) ? candidate : user.email
74
+ end
75
+
76
+ def present?(value)
77
+ return false if value.nil?
78
+ return false if value.respond_to?(:empty?) && value.empty?
79
+
80
+ true
81
+ end
82
+
83
+ def safe_call(user, method)
84
+ user.public_send(method) if user.respond_to?(method)
85
+ end
86
+
87
+ def stringify_keys(hash)
88
+ return {} unless hash.is_a?(Hash)
89
+
90
+ hash.transform_keys(&:to_s)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'verikloak/rails/testing/claims_builder'
4
+ require 'verikloak/rails/testing/middleware_stub'
5
+
6
+ module Verikloak
7
+ module Rails
8
+ module Testing
9
+ # Top-level mix-in for RSpec example groups (request and policy specs).
10
+ # Composes {ClaimsBuilder} and {MiddlewareStub}, and adds
11
+ # `Verikloak::Pundit::UserContext` builders when the optional
12
+ # `verikloak-pundit` gem is loaded.
13
+ module Helpers
14
+ include ClaimsBuilder
15
+ include MiddlewareStub
16
+
17
+ # Build a `Verikloak::Pundit::UserContext` for policy specs.
18
+ #
19
+ # @param user [Object] application user
20
+ # @param claims [Hash] JWT claims (string keys)
21
+ # @return [Verikloak::Pundit::UserContext]
22
+ # @raise [RuntimeError] if `verikloak-pundit` is not loaded
23
+ def build_pundit_user_context(user, claims)
24
+ unless defined?(::Verikloak::Pundit::UserContext)
25
+ raise 'verikloak-pundit gem is not loaded; cannot build a UserContext'
26
+ end
27
+
28
+ ::Verikloak::Pundit::UserContext.new(user, claims)
29
+ end
30
+
31
+ # Convenience wrapper: admin claims + UserContext.
32
+ #
33
+ # @param user [Object]
34
+ # @param admin_group [String]
35
+ # @return [Verikloak::Pundit::UserContext]
36
+ def build_admin_user_context(user, admin_group: '/admin')
37
+ build_pundit_user_context(user, build_admin_claims(user, admin_group: admin_group))
38
+ end
39
+
40
+ # Convenience wrapper: user claims + UserContext.
41
+ #
42
+ # @param user [Object]
43
+ # @param user_group [String]
44
+ # @return [Verikloak::Pundit::UserContext]
45
+ def build_user_user_context(user, user_group: '/user')
46
+ build_pundit_user_context(user, build_user_claims(user, user_group: user_group))
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Verikloak
4
+ module Rails
5
+ module Testing
6
+ # Stubs the Verikloak middleware stack so authenticated request specs
7
+ # can run without contacting an OIDC provider or signing real JWTs.
8
+ #
9
+ # `stub_verikloak_middleware` patches `#call` on the relevant
10
+ # middlewares so they inject the supplied claims into
11
+ # `env['verikloak.user']` and pass through to the next middleware.
12
+ # The companion `verikloak.token` env key is also set when
13
+ # available so controller helpers like `current_token` work.
14
+ #
15
+ # Requires RSpec-style mocks (`allow_any_instance_of`). Load
16
+ # `verikloak/rails/testing/rspec` to wire things up automatically,
17
+ # or include this module directly into your example groups.
18
+ module MiddlewareStub
19
+ DEFAULT_STUB_TOKEN = 'verikloak-test-token'
20
+
21
+ # Stub all loaded Verikloak middlewares to populate the request
22
+ # environment with the supplied claims.
23
+ #
24
+ # @param claims [Hash] value placed into `env['verikloak.user']`
25
+ # @param token [String] value placed into `env['verikloak.token']`
26
+ # @return [void]
27
+ def stub_verikloak_middleware(claims, token: DEFAULT_STUB_TOKEN)
28
+ stub_core_middleware(claims, token)
29
+ stub_bff_middleware(claims, token) if bff_middleware_loaded?
30
+ stub_audience_middleware(claims, token) if audience_middleware_loaded?
31
+ end
32
+
33
+ private
34
+
35
+ def stub_core_middleware(claims, token)
36
+ return unless defined?(::Verikloak::Middleware)
37
+
38
+ install_passthrough_stub(::Verikloak::Middleware, claims, token)
39
+ end
40
+
41
+ def stub_bff_middleware(claims, token)
42
+ install_passthrough_stub(::Verikloak::BFF::HeaderGuard, claims, token)
43
+ end
44
+
45
+ def stub_audience_middleware(claims, token)
46
+ install_passthrough_stub(::Verikloak::Audience::Middleware, claims, token)
47
+ end
48
+
49
+ def install_passthrough_stub(middleware_class, claims, token)
50
+ allow_any_instance_of(middleware_class).to receive(:call) do |instance, env|
51
+ env['verikloak.user'] = claims
52
+ env['verikloak.token'] = token if token
53
+ inner_app = instance.instance_variable_get(:@app)
54
+ inner_app.call(env)
55
+ end
56
+ end
57
+
58
+ def bff_middleware_loaded?
59
+ defined?(::Verikloak::BFF::HeaderGuard)
60
+ end
61
+
62
+ def audience_middleware_loaded?
63
+ defined?(::Verikloak::Audience::Middleware)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec integration for Verikloak::Rails::Testing.
4
+ #
5
+ # Require this file from `spec/rails_helper.rb` (or `spec/spec_helper.rb`)
6
+ # to:
7
+ #
8
+ # 1. Mix {Verikloak::Rails::Testing::Helpers} into request and policy
9
+ # specs. Controller specs (`type: :controller`) are intentionally
10
+ # excluded because they bypass the Rack middleware stack, which is
11
+ # where `stub_verikloak_middleware` injects claims; use a request
12
+ # spec instead, or set `request.env['verikloak.user']` directly when
13
+ # you must use a controller spec.
14
+ # 2. Register the shared contexts:
15
+ # - `"with verikloak admin auth"`
16
+ # - `"with verikloak user auth"`
17
+ # - `"with verikloak custom auth"`
18
+ #
19
+ # The shared contexts assume a `current_user` factory exists in the host
20
+ # application (e.g. `create(:user)`). Override `let(:current_user)` to
21
+ # inject a different user object.
22
+
23
+ require 'verikloak/rails/testing/helpers'
24
+
25
+ raise 'verikloak/rails/testing/rspec requires RSpec' unless defined?(RSpec)
26
+
27
+ RSpec.configure do |config|
28
+ config.include Verikloak::Rails::Testing::Helpers, type: :request
29
+ config.include Verikloak::Rails::Testing::Helpers, type: :policy
30
+ end
31
+
32
+ RSpec.shared_context 'with verikloak admin auth' do
33
+ let(:current_user) { create(:user) }
34
+
35
+ before do
36
+ stub_verikloak_middleware(build_admin_claims(current_user))
37
+ end
38
+ end
39
+
40
+ RSpec.shared_context 'with verikloak user auth' do
41
+ let(:current_user) { create(:user) }
42
+
43
+ before do
44
+ stub_verikloak_middleware(build_user_claims(current_user))
45
+ end
46
+ end
47
+
48
+ RSpec.shared_context 'with verikloak custom auth' do
49
+ let(:current_user) { create(:user) }
50
+ let(:verikloak_groups) { [] }
51
+ let(:verikloak_extra_claims) { {} }
52
+
53
+ before do
54
+ stub_verikloak_middleware(
55
+ build_jwt_claims(current_user, groups: verikloak_groups, extra_claims: verikloak_extra_claims)
56
+ )
57
+ end
58
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Verikloak
4
+ module Rails
5
+ # Test-support helpers for application specs that exercise endpoints
6
+ # protected by Verikloak.
7
+ #
8
+ # Submodules:
9
+ # - {ClaimsBuilder} – build JWT-shaped Hashes from a user-like
10
+ # object. Pure Ruby, no test-framework dependency, so it can be
11
+ # used standalone (e.g. from `Minitest`).
12
+ # - {MiddlewareStub} – stub Verikloak/BFF/Audience middleware to
13
+ # inject pre-built claims into `env['verikloak.user']`. Requires
14
+ # RSpec mocks (`allow_any_instance_of`); not usable from
15
+ # `Minitest` without bringing in `rspec-mocks`.
16
+ # - {Helpers} – mixes in {ClaimsBuilder} and {MiddlewareStub}
17
+ # and adds Pundit `UserContext` builders when `verikloak-pundit`
18
+ # is loaded.
19
+ #
20
+ # The simplest way to wire these into an RSpec suite is to require
21
+ # `verikloak/rails/testing/rspec` from `spec/rails_helper.rb`.
22
+ module Testing
23
+ autoload :ClaimsBuilder, 'verikloak/rails/testing/claims_builder'
24
+ autoload :MiddlewareStub, 'verikloak/rails/testing/middleware_stub'
25
+ autoload :Helpers, 'verikloak/rails/testing/helpers'
26
+ end
27
+ end
28
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Verikloak
4
4
  module Rails
5
- VERSION = '1.0.1'
5
+ VERSION = '1.1.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: verikloak-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - taiyaky
@@ -83,6 +83,11 @@ files:
83
83
  - lib/verikloak/rails/railtie.rb
84
84
  - lib/verikloak/rails/railtie_logger.rb
85
85
  - lib/verikloak/rails/skip_path_checker.rb
86
+ - lib/verikloak/rails/testing.rb
87
+ - lib/verikloak/rails/testing/claims_builder.rb
88
+ - lib/verikloak/rails/testing/helpers.rb
89
+ - lib/verikloak/rails/testing/middleware_stub.rb
90
+ - lib/verikloak/rails/testing/rspec.rb
86
91
  - lib/verikloak/rails/version.rb
87
92
  homepage: https://github.com/taiyaky/verikloak-rails
88
93
  licenses:
@@ -91,7 +96,7 @@ metadata:
91
96
  source_code_uri: https://github.com/taiyaky/verikloak-rails
92
97
  changelog_uri: https://github.com/taiyaky/verikloak-rails/blob/main/CHANGELOG.md
93
98
  bug_tracker_uri: https://github.com/taiyaky/verikloak-rails/issues
94
- documentation_uri: https://rubydoc.info/gems/verikloak-rails/1.0.1
99
+ documentation_uri: https://rubydoc.info/gems/verikloak-rails/1.1.0
95
100
  rubygems_mfa_required: 'true'
96
101
  rdoc_options: []
97
102
  require_paths: