verikloak-audience 0.1.0 → 0.1.1

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: 983947348061c7b7dd379572850ec12de6bddda37b511b03466301b22c15256f
4
- data.tar.gz: 2bd99a2a76fdc020d4f3a51a1db11151d0b38e9429847294f0431abb7e47dd17
3
+ metadata.gz: 5ff2e2e97325963725431051763d956fad12ae1b2ea04476d849a8f1d085e8e6
4
+ data.tar.gz: c9ca99b0c0b0c0089abc3f854a8a50234000d27a93ae7092f322ac1c2283e59e
5
5
  SHA512:
6
- metadata.gz: 6bca90d90c7d35408009fcd6007ac317115743eda14dc4e8f1e9853a3fdb9350f4cb5db426e032631faf2e4ef9f96a86885942145bad40d7e809b5eb1a748905
7
- data.tar.gz: 3bcc1d7a44a094a3e15d8b33e79fe46ac66e21eb84175799d0469f701522949340e82b43df0d6e029a7396febc9c0ab16a196efdc1cce6bb25358b674019463e
6
+ metadata.gz: 36f0c7cbddc7fcc7f2eea2a90743a5c7abd9a3d03b9f279a0bf35c99e77557af8f579e847c5169c56ecdc50794f5b99058eecc866565f03f2796e6e5a694e243
7
+ data.tar.gz: 6e908e9720198cf36a40b91bd3b345c6ed622805cf7d10b53af0dd94c76a137fa94b64f66d7858aa1a7c007612445154c9c0a61a445eaa3930fd72a313b4febf
data/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.1.1] - 2025-09-20
11
+
12
+ ### Changed
13
+ - Documented `Configuration#safe_dup` behaviour and error handling in YARD.
14
+ - Expanded error class documentation for clearer release guidance.
15
+
10
16
  ## [0.1.0] - 2025-09-20
11
17
 
12
18
  ### Added
@@ -7,6 +7,8 @@ module Verikloak
7
7
  # This module provides predicate helpers used by the middleware to decide
8
8
  # whether a given set of claims satisfies the configured profile.
9
9
  module Checker
10
+ VALID_PROFILES = %i[strict_single allow_account resource_or_aud].freeze
11
+
10
12
  module_function
11
13
 
12
14
  # Returns whether the given claims satisfy the configured profile.
@@ -15,8 +17,16 @@ module Verikloak
15
17
  # @param cfg [Verikloak::Audience::Configuration]
16
18
  # @return [Boolean]
17
19
  def ok?(claims, cfg)
18
- profile = cfg.profile.to_sym
19
- profile = :strict_single unless %i[strict_single allow_account resource_or_aud].include?(profile)
20
+ claims = normalize_claims(claims)
21
+
22
+ profile = cfg.profile
23
+ profile = profile.to_sym if profile.respond_to?(:to_sym)
24
+ profile = :strict_single if profile.nil?
25
+
26
+ unless VALID_PROFILES.include?(profile)
27
+ raise Verikloak::Audience::ConfigurationError,
28
+ "unknown audience profile #{cfg.profile.inspect}"
29
+ end
20
30
 
21
31
  case profile
22
32
  when :strict_single
@@ -77,6 +87,8 @@ module Verikloak
77
87
  # @param cfg [Verikloak::Audience::Configuration]
78
88
  # @return [:strict_single, :allow_account, :resource_or_aud]
79
89
  def suggest(claims, cfg)
90
+ claims = normalize_claims(claims)
91
+
80
92
  aud = Array(claims['aud']).map(&:to_s)
81
93
  req = cfg.required_aud_list
82
94
  has_roles = !Array(claims.dig('resource_access', cfg.resource_client.to_s, 'roles')).empty?
@@ -87,6 +99,27 @@ module Verikloak
87
99
 
88
100
  :strict_single
89
101
  end
102
+
103
+ # Normalize incoming claims to a Hash to guard against unexpected
104
+ # env payloads or middleware ordering issues.
105
+ #
106
+ # @param claims [Object]
107
+ # @return [Hash]
108
+ def normalize_claims(claims)
109
+ return {} if claims.nil?
110
+ return claims if claims.is_a?(Hash)
111
+
112
+ if claims.respond_to?(:to_hash)
113
+ coerced = claims.to_hash
114
+ return coerced if coerced.is_a?(Hash)
115
+ end
116
+
117
+ {}
118
+ rescue StandardError
119
+ {}
120
+ end
121
+ module_function :normalize_claims
122
+ private_class_method :normalize_claims
90
123
  end
91
124
  end
92
125
  end
@@ -21,7 +21,8 @@ module Verikloak
21
21
  # @return [Boolean]
22
22
  class Configuration
23
23
  attr_accessor :profile, :required_aud, :resource_client,
24
- :env_claims_key, :suggest_in_logs
24
+ :suggest_in_logs
25
+ attr_reader :env_claims_key
25
26
 
26
27
  # Create a configuration with safe defaults.
27
28
  #
@@ -30,16 +31,58 @@ module Verikloak
30
31
  @profile = :strict_single
31
32
  @required_aud = []
32
33
  @resource_client = 'rails-api'
33
- @env_claims_key = 'verikloak.user'
34
+ self.env_claims_key = 'verikloak.user'
34
35
  @suggest_in_logs = true
35
36
  end
36
37
 
38
+ # Ensure `dup` produces an independent copy.
39
+ #
40
+ # @param source [Configuration]
41
+ # @return [void]
42
+ def initialize_copy(source)
43
+ super
44
+ @profile = safe_dup(source.profile)
45
+ @required_aud = duplicate_required_aud(source.required_aud)
46
+ @resource_client = safe_dup(source.resource_client)
47
+ self.env_claims_key = safe_dup(source.env_claims_key)
48
+ @suggest_in_logs = source.suggest_in_logs
49
+ end
50
+
37
51
  # Coerce `required_aud` into an array of strings.
38
52
  #
39
53
  # @return [Array<String>]
40
54
  def required_aud_list
41
55
  Array(required_aud).map(&:to_s)
42
56
  end
57
+
58
+ # @param value [#to_s, nil]
59
+ # @return [void]
60
+ def env_claims_key=(value)
61
+ @env_claims_key = value&.to_s
62
+ end
63
+
64
+ private
65
+
66
+ # Attempt to duplicate a value while tolerating non-duplicable inputs.
67
+ # Returns `nil` when given nil and falls back to the original on duplication errors.
68
+ #
69
+ # @param value [Object, nil]
70
+ # @return [Object, nil]
71
+ def safe_dup(value)
72
+ return if value.nil?
73
+
74
+ value.dup
75
+ rescue TypeError
76
+ value
77
+ end
78
+
79
+ def duplicate_required_aud(value)
80
+ return if value.nil?
81
+
82
+ return value.map { |item| safe_dup(item) } if value.is_a?(Array)
83
+
84
+ safe_dup(value)
85
+ end
43
86
  end
44
87
  end
45
88
  end
@@ -23,12 +23,30 @@ module Verikloak
23
23
  end
24
24
  end
25
25
 
26
- # Raised when audience is insufficient for the configured profile.
26
+ # Raised when verified claims do not satisfy the configured profile.
27
+ # Typically emitted when the required audience list is empty or
28
+ # mismatches the token audiences.
27
29
  class Forbidden < Error
28
- # @param msg [String]
30
+ # Build a forbidden error with a customizable message while preserving
31
+ # the standard machine-friendly code and HTTP status.
32
+ #
33
+ # @param msg [String] alternate human-readable explanation
29
34
  def initialize(msg = 'insufficient audience')
30
35
  super(msg, code: 'insufficient_audience', http_status: 403)
31
36
  end
32
37
  end
38
+
39
+ # Raised when configuration is invalid.
40
+ # Used when runtime configuration checks detect missing or incompatible
41
+ # values before audience validation takes place.
42
+ class ConfigurationError < Error
43
+ # Build a configuration error while keeping a consistent error code
44
+ # and a 500 HTTP status to signal an internal misconfiguration.
45
+ #
46
+ # @param msg [String] alternate human-readable explanation
47
+ def initialize(msg = 'invalid audience configuration')
48
+ super(msg, code: 'audience_configuration_error', http_status: 500)
49
+ end
50
+ end
33
51
  end
34
52
  end
@@ -22,7 +22,7 @@ module Verikloak
22
22
  # @option opts [Boolean] :suggest_in_logs
23
23
  def initialize(app, **opts)
24
24
  @app = app
25
- @config = Verikloak::Audience.configure
25
+ @config = Verikloak::Audience.config.dup
26
26
  apply_overrides!(opts)
27
27
  end
28
28
 
@@ -31,7 +31,8 @@ module Verikloak
31
31
  # @param env [Hash] Rack environment
32
32
  # @return [Array(Integer, Hash, #each)] Rack response triple
33
33
  def call(env)
34
- claims = env[@config.env_claims_key] || {}
34
+ env_key = @config.env_claims_key
35
+ claims = env[env_key] || env[env_key&.to_sym] || {}
35
36
  return @app.call(env) if Checker.ok?(claims, @config)
36
37
 
37
38
  if @config.suggest_in_logs
@@ -54,8 +55,16 @@ module Verikloak
54
55
  # @return [void]
55
56
  def apply_overrides!(opts)
56
57
  cfg = @config
58
+ opts.each_key do |key|
59
+ writer = "#{key}="
60
+ next if cfg.respond_to?(writer)
61
+
62
+ raise Verikloak::Audience::ConfigurationError,
63
+ "unknown middleware option :#{key}"
64
+ end
65
+
57
66
  opts.each do |k, v|
58
- cfg.public_send("#{k}=", v) if cfg.respond_to?("#{k}=")
67
+ cfg.public_send("#{k}=", v)
59
68
  end
60
69
  end
61
70
  end
@@ -4,6 +4,6 @@ module Verikloak
4
4
  module Audience
5
5
  # Current gem version.
6
6
  # @return [String]
7
- VERSION = '0.1.0'
7
+ VERSION = '0.1.1'
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: verikloak-audience
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - taiyaky
@@ -74,7 +74,7 @@ metadata:
74
74
  source_code_uri: https://github.com/taiyaky/verikloak-audience
75
75
  changelog_uri: https://github.com/taiyaky/verikloak-audience/blob/main/CHANGELOG.md
76
76
  bug_tracker_uri: https://github.com/taiyaky/verikloak-audience/issues
77
- documentation_uri: https://rubydoc.info/gems/verikloak-audience/0.1.0
77
+ documentation_uri: https://rubydoc.info/gems/verikloak-audience/0.1.1
78
78
  rubygems_mfa_required: 'true'
79
79
  rdoc_options: []
80
80
  require_paths: