verikloak-pundit 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9062ddd03a6b118971608756c82d1299dc4b509d7219d289aefbeaf381c8e49e
4
- data.tar.gz: f2c2a0e9a236c454aabd9316b04ee6e1e98860f6d2db3d8e3863fb826d92a29f
3
+ metadata.gz: 6936cd5efb5ccaecf789b427db2f7cd09f358604ba334f592f3e5079ac6501c4
4
+ data.tar.gz: 37db6dc08acd9918bebb15bd8dbb32320e00661ffc81b1b40c85850ec1c0822c
5
5
  SHA512:
6
- metadata.gz: b2a830375513deb2e8d4b350ffb7960fb95a6c58d5b45af7c519b45cd6086ffd21230af26a89921fca57528e48275e4bf4be59d96a2c45794a11d262c7fe80c3
7
- data.tar.gz: deebae419df84ced4bb9ff0a9181847155db0adeb08f6b8cfb8319c6b2f50cd99cbd8e2dc2fa89417d15f2ab198c8e262503be630ab9ec3daf1ca58c5de7aeef
6
+ metadata.gz: 120391c41f3f769ce49d7bddd83bed04b010ea367d8b63ddedb5de4610f9f8a6600f29f56f84c929b2209e4c26d6825a3d774ae1f32c20f16dd6d53378d5f9bf
7
+ data.tar.gz: a6d8babf7f27a842105fcc9ba44a42f9c73a21c45eeb486339c9afddca8aa1a6df97720c944407945ebd02a7918455bbb235b3e74de39025594d50d8babf0014
data/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.2.0] - 2025-09-21
11
+
12
+ ### Added
13
+ - Allow `permission_role_scope = :all_resources` to respect the new
14
+ `permission_resource_clients` whitelist so only approved clients contribute
15
+ to permission checks.
16
+ - Document verikloak-bff and verikloak-audience integration patterns,
17
+ including `env_claims_key` examples and role naming guidance.
18
+
19
+ ### Changed
20
+ - `UserContext` now snapshots the configuration at initialization to keep
21
+ behavior consistent even if `Verikloak::Pundit.configure` runs mid-request.
22
+ - Bump the minimum `verikloak` runtime dependency to `>= 0.1.5` to pick up
23
+ client whitelist support.
24
+
10
25
  ## [0.1.1] - 2025-09-20
11
26
 
12
27
  ### Added
data/README.md CHANGED
@@ -89,12 +89,35 @@ Verikloak::Pundit.configure do |c|
89
89
  # (enabling this broadens permissions to every resource client;
90
90
  # review the upstream role assignments before turning it on)
91
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
92
97
 
93
98
  # Expose `verikloak_claims` to views via helper_method (Rails only)
94
99
  c.expose_helper_method = true
95
100
  end
96
101
  ```
97
102
 
103
+ ### Working with other Verikloak gems
104
+
105
+ - **verikloak-bff**: When your Rails application sits behind the BFF, the access
106
+ token presented to verikloak-pundit typically originates from the BFF
107
+ (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.
114
+ - **verikloak-audience**: Audience services often mint resource roles with a
115
+ service-specific prefix (for example, `audience-service:editor`). Align your
116
+ `role_map` keys with that naming convention so `user.has_permission?` resolves
117
+ correctly. If Audience adds its own client entry inside `resource_access`, add
118
+ that client id to `permission_resource_clients` when you need to consume those
119
+ roles from Rails.
120
+
98
121
  ## Non-Rails / custom usage
99
122
 
100
123
  ```ruby
@@ -142,6 +165,10 @@ If you find a security vulnerability, please follow the instructions in [SECURIT
142
165
 
143
166
  ### Operational guidance
144
167
  - 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.
168
+ - Combine `permission_role_scope = :all_resources` with `permission_resource_clients`
169
+ to explicitly opt-in the clients that may contribute permissions. Leaving the
170
+ whitelist blank (the default) reverts to the legacy behavior of trusting
171
+ every client in the token.
145
172
  - 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.
146
173
 
147
174
  ## License
@@ -11,7 +11,6 @@ module Verikloak
11
11
  desc 'Creates Verikloak Pundit initializer and a base ApplicationPolicy (optional).'
12
12
 
13
13
  # Skip creating application_policy.rb
14
- # @return [Boolean]
15
14
  class_option :skip_policy, type: :boolean, default: false, desc: 'Do not create application_policy.rb'
16
15
 
17
16
  # Create the initializer file under config/initializers.
@@ -16,12 +16,16 @@ module Verikloak
16
16
  # @return [Array<String,Proc>] path inside JWT claims to reach resource roles
17
17
  # @!attribute permission_role_scope
18
18
  # @return [Symbol] :default_resource or :all_resources for permission mapping scope
19
+ # @!attribute permission_resource_clients
20
+ # @return [Array<String>, nil] list of resource clients allowed when
21
+ # {#permission_role_scope} is `:all_resources`. `nil` permits every client.
19
22
  # @!attribute expose_helper_method
20
23
  # @return [Boolean] whether to register `verikloak_claims` as a Rails helper method
21
24
  class Configuration
22
25
  attr_accessor :resource_client, :role_map, :env_claims_key,
23
26
  :realm_roles_path, :resource_roles_path,
24
- :permission_role_scope, :expose_helper_method
27
+ :permission_role_scope, :permission_resource_clients,
28
+ :expose_helper_method
25
29
 
26
30
  # Build a new configuration, optionally copying values from another
27
31
  # configuration so callers can mutate a safe duplicate.
@@ -64,6 +68,7 @@ module Verikloak
64
68
  @role_map = dup_hash(@role_map).freeze
65
69
  @realm_roles_path = dup_array(@realm_roles_path).freeze
66
70
  @resource_roles_path = dup_array(@resource_roles_path).freeze
71
+ @permission_resource_clients = freeze_permission_clients(@permission_resource_clients)
67
72
  @expose_helper_method = !@expose_helper_method.nil? && @expose_helper_method
68
73
  freeze
69
74
  end
@@ -81,6 +86,7 @@ module Verikloak
81
86
  # rubocop:enable Style/SymbolProc
82
87
  # :default_resource (realm + default client), :all_resources (realm + all clients)
83
88
  @permission_role_scope = :default_resource
89
+ @permission_resource_clients = nil
84
90
  @expose_helper_method = true
85
91
  end
86
92
 
@@ -95,6 +101,7 @@ module Verikloak
95
101
  @realm_roles_path = dup_array(other.realm_roles_path)
96
102
  @resource_roles_path = dup_array(other.resource_roles_path)
97
103
  @permission_role_scope = other.permission_role_scope
104
+ @permission_resource_clients = dup_array(other.permission_resource_clients)
98
105
  @expose_helper_method = other.expose_helper_method
99
106
  end
100
107
 
@@ -178,6 +185,17 @@ module Verikloak
178
185
 
179
186
  value.respond_to?(:dup)
180
187
  end
188
+
189
+ # Normalize and freeze the configured permission clients list.
190
+ #
191
+ # @param value [Array<String, Symbol>, nil]
192
+ # @return [Array<String>, nil]
193
+ def freeze_permission_clients(value)
194
+ array = dup_array(value)
195
+ return nil if array.nil?
196
+
197
+ array.compact.map(&:to_s).uniq.freeze
198
+ end
181
199
  end
182
200
  end
183
201
  end
@@ -4,15 +4,17 @@ module Verikloak
4
4
  module Pundit
5
5
  # Lightweight wrapper around Keycloak claims for Pundit policies.
6
6
  class UserContext
7
- attr_reader :claims, :resource_client
7
+ attr_reader :claims, :resource_client, :config
8
8
 
9
9
  # Create a new user context from JWT claims.
10
10
  #
11
11
  # @param claims [Hash] JWT claims issued by Keycloak
12
12
  # @param resource_client [String] default resource client name for resource roles
13
- def initialize(claims, resource_client: Verikloak::Pundit.config.resource_client)
13
+ # @param config [Verikloak::Pundit::Configuration] configuration snapshot to use
14
+ def initialize(claims, resource_client: nil, config: nil)
15
+ @config = config || Verikloak::Pundit.config
14
16
  @claims = claims || {}
15
- @resource_client = resource_client.to_s
17
+ @resource_client = (resource_client || @config.resource_client).to_s
16
18
  end
17
19
 
18
20
  # Subject identifier from claims.
@@ -30,7 +32,7 @@ module Verikloak
30
32
  # Realm-level roles from claims based on configuration path.
31
33
  # @return [Array<String>]
32
34
  def realm_roles
33
- path = resolve_path(Verikloak::Pundit.config.realm_roles_path)
35
+ path = resolve_path(config.realm_roles_path)
34
36
  Array(claims.dig(*path))
35
37
  end
36
38
 
@@ -40,7 +42,7 @@ module Verikloak
40
42
  # @return [Array<String>]
41
43
  def resource_roles(client = resource_client)
42
44
  client = client.to_s
43
- path = resolve_path(Verikloak::Pundit.config.resource_roles_path, client: client)
45
+ path = resolve_path(config.resource_roles_path, client: client)
44
46
  Array(claims.dig(*path))
45
47
  end
46
48
 
@@ -82,7 +84,7 @@ module Verikloak
82
84
  def has_permission?(perm) # rubocop:disable Naming/PredicatePrefix
83
85
  pr = perm.to_sym
84
86
  roles = realm_roles + resource_roles_scope
85
- mapped = roles.map { |r| RoleMapper.map(r, Verikloak::Pundit.config) }
87
+ mapped = roles.map { |r| RoleMapper.map(r, config) }
86
88
  mapped.map(&:to_sym).include?(pr)
87
89
  end
88
90
 
@@ -91,8 +93,9 @@ module Verikloak
91
93
  # @param env [Hash] Rack environment
92
94
  # @return [UserContext]
93
95
  def self.from_env(env)
94
- claims = env[Verikloak::Pundit.config.env_claims_key]
95
- new(claims)
96
+ config = Verikloak::Pundit.config
97
+ claims = env[config.env_claims_key]
98
+ new(claims, config: config)
96
99
  end
97
100
 
98
101
  private
@@ -108,9 +111,9 @@ module Verikloak
108
111
  when Proc
109
112
  # Support lambdas that accept (config) or (config, client)
110
113
  if seg.arity >= 2
111
- seg.call(Verikloak::Pundit.config, client).to_s
114
+ seg.call(config, client).to_s
112
115
  else
113
- seg.call(Verikloak::Pundit.config).to_s
116
+ seg.call(config).to_s
114
117
  end
115
118
  else
116
119
  seg.to_s
@@ -121,7 +124,7 @@ module Verikloak
121
124
  # Resolve resource roles based on configured permission scope.
122
125
  # @return [Array<String>]
123
126
  def resource_roles_scope
124
- case Verikloak::Pundit.config.permission_role_scope&.to_sym
127
+ case config.permission_role_scope&.to_sym
125
128
  when :all_resources
126
129
  resource_roles_all_clients
127
130
  else
@@ -137,7 +140,22 @@ module Verikloak
137
140
 
138
141
  # Bypass configured path lambda (which targets the default client)
139
142
  # and gather roles from all clients explicitly.
140
- access.values.flat_map { |entry| Array(entry['roles']) }
143
+ access.each_with_object([]) do |(client_id, entry), roles|
144
+ next unless permission_client_allowed?(client_id)
145
+
146
+ roles.concat(Array(entry['roles']))
147
+ end
148
+ end
149
+
150
+ # Check whether the given client is allowed for permission scope.
151
+ #
152
+ # @param client_id [String]
153
+ # @return [Boolean]
154
+ def permission_client_allowed?(client_id)
155
+ whitelist = config.permission_resource_clients
156
+ return true if whitelist.nil?
157
+
158
+ whitelist.include?(client_id.to_s)
141
159
  end
142
160
  end
143
161
  end
@@ -5,6 +5,6 @@ module Verikloak
5
5
  # Gem version for verikloak-pundit.
6
6
  #
7
7
  # @return [String]
8
- VERSION = '0.1.1'
8
+ VERSION = '0.2.0'
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.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - taiyaky
@@ -49,20 +49,20 @@ dependencies:
49
49
  requirements:
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
- version: 0.1.2
52
+ version: 0.1.5
53
53
  - - "<"
54
54
  - !ruby/object:Gem::Version
55
- version: '0.2'
55
+ version: 1.0.0
56
56
  type: :runtime
57
57
  prerelease: false
58
58
  version_requirements: !ruby/object:Gem::Requirement
59
59
  requirements:
60
60
  - - ">="
61
61
  - !ruby/object:Gem::Version
62
- version: 0.1.2
62
+ version: 0.1.5
63
63
  - - "<"
64
64
  - !ruby/object:Gem::Version
65
- version: '0.2'
65
+ version: 1.0.0
66
66
  description: Maps Keycloak JWT roles to a Pundit-friendly UserContext with helpers
67
67
  and a Rails generator.
68
68
  executables: []
@@ -92,7 +92,7 @@ metadata:
92
92
  source_code_uri: https://github.com/taiyaky/verikloak-pundit
93
93
  changelog_uri: https://github.com/taiyaky/verikloak-pundit/blob/main/CHANGELOG.md
94
94
  bug_tracker_uri: https://github.com/taiyaky/verikloak-pundit/issues
95
- documentation_uri: https://rubydoc.info/gems/verikloak-pundit/0.1.1
95
+ documentation_uri: https://rubydoc.info/gems/verikloak-pundit/0.2.0
96
96
  rubygems_mfa_required: 'true'
97
97
  rdoc_options: []
98
98
  require_paths: