Class: Verikloak::Audience::Configuration
- Inherits:
-
Object
- Object
- Verikloak::Audience::Configuration
- Defined in:
- lib/verikloak/audience/configuration.rb
Overview
Configuration holder for verikloak-audience.
Constant Summary collapse
- DEFAULT_RESOURCE_CLIENT =
'rails-api'- DEFAULT_ENV_CLAIMS_KEY =
'verikloak.user'- DEFAULT_PROFILE =
:strict_single- VALID_PROFILES =
Enforcement profiles supported by Verikloak::Audience::Checker.
%i[strict_single allow_account any_match resource_or_aud].freeze
Instance Attribute Summary collapse
-
#env_claims_key ⇒ String
Rack env key from which to read verified claims.
-
#profile ⇒ :strict_single, ...
The enforcement profile to use.
-
#required_aud ⇒ Array<String,Symbol>, ...
Required audience(s).
-
#resource_client ⇒ String
Client id to use for
resource_access[client].roleslookup. -
#skip_paths ⇒ Array<String, Regexp>
Paths to skip audience validation for (e.g., health checks).
-
#suggest_in_logs ⇒ Boolean
Whether to log a suggestion when audience validation fails.
Instance Method Summary collapse
-
#initialize ⇒ void
constructor
Create a configuration with safe defaults.
-
#initialize_copy(source) ⇒ void
Ensure
dupproduces an independent copy. -
#normalized_profile ⇒ Symbol, Object
Coerce
profileinto a Symbol, falling back to the default when unset. -
#required_aud_list ⇒ Array<String>
Coerce
required_audinto an array of strings. -
#validate! ⇒ Configuration
Validate the configuration to ensure required values are present.
-
#validated_profile ⇒ Symbol
Coerce
profileinto a Symbol and ensure it is one of VALID_PROFILES.
Constructor Details
#initialize ⇒ void
Create a configuration with safe defaults.
43 44 45 46 47 48 49 50 |
# File 'lib/verikloak/audience/configuration.rb', line 43 def initialize @profile = DEFAULT_PROFILE @required_aud = [] @resource_client = DEFAULT_RESOURCE_CLIENT self.env_claims_key = DEFAULT_ENV_CLAIMS_KEY @suggest_in_logs = true @skip_paths = [] end |
Instance Attribute Details
#env_claims_key ⇒ String
Rack env key from which to read verified claims.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/verikloak/audience/configuration.rb', line 28 class Configuration DEFAULT_RESOURCE_CLIENT = 'rails-api' DEFAULT_ENV_CLAIMS_KEY = 'verikloak.user' DEFAULT_PROFILE = :strict_single # Enforcement profiles supported by {Verikloak::Audience::Checker}. VALID_PROFILES = %i[strict_single allow_account any_match resource_or_aud].freeze attr_accessor :profile, :required_aud, :resource_client, :suggest_in_logs, :skip_paths attr_reader :env_claims_key # Create a configuration with safe defaults. # # @return [void] def initialize @profile = DEFAULT_PROFILE @required_aud = [] @resource_client = DEFAULT_RESOURCE_CLIENT self.env_claims_key = DEFAULT_ENV_CLAIMS_KEY @suggest_in_logs = true @skip_paths = [] end # Ensure `dup` produces an independent copy. # # @param source [Configuration] # @return [void] def initialize_copy(source) super @profile = safe_dup(source.profile) @required_aud = duplicate_required_aud(source.required_aud) @resource_client = safe_dup(source.resource_client) self.env_claims_key = safe_dup(source.env_claims_key) @suggest_in_logs = source.suggest_in_logs @skip_paths = source.skip_paths&.dup || [] end # Coerce `required_aud` into an array of strings. # # @return [Array<String>] def required_aud_list Array(required_aud).map(&:to_s) end # Coerce `profile` into a Symbol, falling back to the default when unset. # Values that cannot be symbolized (e.g. an Integer) are returned as-is # so {#validated_profile} can reject them with a precise error. The # result is not guaranteed to be a member of {VALID_PROFILES}; use # {#validated_profile} to enforce that. # # @return [Symbol, Object] def normalized_profile value = profile value = value.to_sym if value.respond_to?(:to_sym) value.nil? ? DEFAULT_PROFILE : value end # Coerce `profile` into a Symbol and ensure it is one of # {VALID_PROFILES}. Single source of the unknown-profile check shared # by {#validate!} and {Verikloak::Audience::Checker.ok?}. # # @raise [ConfigurationError] when the profile is unknown # @return [Symbol] def validated_profile profile_name = normalized_profile unless VALID_PROFILES.include?(profile_name) raise Verikloak::Audience::ConfigurationError, "unknown audience profile #{profile.inspect}" end profile_name end # @param value [#to_s, nil] # @raise [ConfigurationError] when value is nil or blank # @return [void] def env_claims_key=(value) str = value&.to_s stripped = str&.strip if stripped.nil? || stripped.empty? raise Verikloak::Audience::ConfigurationError, 'env_claims_key must not be nil or empty' end @env_claims_key = stripped end # Validate the configuration to ensure required values are present. # # @raise [ConfigurationError] when `required_aud` is empty, `profile` is # not one of {VALID_PROFILES}, or `resource_client` conflicts with # `required_aud` under the `:resource_or_aud` profile # @return [Configuration] the validated configuration def validate! audiences = required_aud_list if audiences.empty? raise Verikloak::Audience::ConfigurationError, 'required_aud must include at least one audience' end profile_name = validated_profile ensure_resource_client!(audiences) if profile_name == :resource_or_aud self end private # Attempt to duplicate a value while tolerating non-duplicable inputs. # Returns `nil` when given nil and falls back to the original on duplication errors. # # @param value [Object, nil] # @return [Object, nil] def safe_dup(value) return if value.nil? value.dup rescue TypeError value end # Build a deep-ish copy of `required_aud` so that mutations on copies # do not leak back into the original configuration instance. # # @param value [Array<String,Symbol>, String, Symbol, nil] # @return [Array<String,Symbol>, String, Symbol, nil] def duplicate_required_aud(value) return if value.nil? return value.map { |item| safe_dup(item) } if value.is_a?(Array) safe_dup(value) end # Ensure that the configured `resource_client` fits the `required_aud` # list when the :resource_or_aud profile is active. Attempts to infer # the client id from `required_aud` when possible and raises when # ambiguity remains. # # @param audiences [Array<String>] coerced required audiences # @return [void] def ensure_resource_client!(audiences) client = resource_client.to_s error_msg = 'resource_client must match one of required_aud when using :resource_or_aud profile' if needs_resource_client_inference?(client, audiences) raise Verikloak::Audience::ConfigurationError, error_msg unless audiences.one? self.resource_client = audiences.first client = resource_client.to_s end return if audiences.include?(client) raise Verikloak::Audience::ConfigurationError, error_msg end # Decide whether the resource client should be inferred from the # required audiences based on the current client value. # # @param client [String] # @param audiences [Array<String>] # @return [Boolean] def needs_resource_client_inference?(client, audiences) client.empty? || (client == DEFAULT_RESOURCE_CLIENT && !audiences.include?(client)) end end |
#profile ⇒ :strict_single, ...
The enforcement profile to use.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/verikloak/audience/configuration.rb', line 28 class Configuration DEFAULT_RESOURCE_CLIENT = 'rails-api' DEFAULT_ENV_CLAIMS_KEY = 'verikloak.user' DEFAULT_PROFILE = :strict_single # Enforcement profiles supported by {Verikloak::Audience::Checker}. VALID_PROFILES = %i[strict_single allow_account any_match resource_or_aud].freeze attr_accessor :profile, :required_aud, :resource_client, :suggest_in_logs, :skip_paths attr_reader :env_claims_key # Create a configuration with safe defaults. # # @return [void] def initialize @profile = DEFAULT_PROFILE @required_aud = [] @resource_client = DEFAULT_RESOURCE_CLIENT self.env_claims_key = DEFAULT_ENV_CLAIMS_KEY @suggest_in_logs = true @skip_paths = [] end # Ensure `dup` produces an independent copy. # # @param source [Configuration] # @return [void] def initialize_copy(source) super @profile = safe_dup(source.profile) @required_aud = duplicate_required_aud(source.required_aud) @resource_client = safe_dup(source.resource_client) self.env_claims_key = safe_dup(source.env_claims_key) @suggest_in_logs = source.suggest_in_logs @skip_paths = source.skip_paths&.dup || [] end # Coerce `required_aud` into an array of strings. # # @return [Array<String>] def required_aud_list Array(required_aud).map(&:to_s) end # Coerce `profile` into a Symbol, falling back to the default when unset. # Values that cannot be symbolized (e.g. an Integer) are returned as-is # so {#validated_profile} can reject them with a precise error. The # result is not guaranteed to be a member of {VALID_PROFILES}; use # {#validated_profile} to enforce that. # # @return [Symbol, Object] def normalized_profile value = profile value = value.to_sym if value.respond_to?(:to_sym) value.nil? ? DEFAULT_PROFILE : value end # Coerce `profile` into a Symbol and ensure it is one of # {VALID_PROFILES}. Single source of the unknown-profile check shared # by {#validate!} and {Verikloak::Audience::Checker.ok?}. # # @raise [ConfigurationError] when the profile is unknown # @return [Symbol] def validated_profile profile_name = normalized_profile unless VALID_PROFILES.include?(profile_name) raise Verikloak::Audience::ConfigurationError, "unknown audience profile #{profile.inspect}" end profile_name end # @param value [#to_s, nil] # @raise [ConfigurationError] when value is nil or blank # @return [void] def env_claims_key=(value) str = value&.to_s stripped = str&.strip if stripped.nil? || stripped.empty? raise Verikloak::Audience::ConfigurationError, 'env_claims_key must not be nil or empty' end @env_claims_key = stripped end # Validate the configuration to ensure required values are present. # # @raise [ConfigurationError] when `required_aud` is empty, `profile` is # not one of {VALID_PROFILES}, or `resource_client` conflicts with # `required_aud` under the `:resource_or_aud` profile # @return [Configuration] the validated configuration def validate! audiences = required_aud_list if audiences.empty? raise Verikloak::Audience::ConfigurationError, 'required_aud must include at least one audience' end profile_name = validated_profile ensure_resource_client!(audiences) if profile_name == :resource_or_aud self end private # Attempt to duplicate a value while tolerating non-duplicable inputs. # Returns `nil` when given nil and falls back to the original on duplication errors. # # @param value [Object, nil] # @return [Object, nil] def safe_dup(value) return if value.nil? value.dup rescue TypeError value end # Build a deep-ish copy of `required_aud` so that mutations on copies # do not leak back into the original configuration instance. # # @param value [Array<String,Symbol>, String, Symbol, nil] # @return [Array<String,Symbol>, String, Symbol, nil] def duplicate_required_aud(value) return if value.nil? return value.map { |item| safe_dup(item) } if value.is_a?(Array) safe_dup(value) end # Ensure that the configured `resource_client` fits the `required_aud` # list when the :resource_or_aud profile is active. Attempts to infer # the client id from `required_aud` when possible and raises when # ambiguity remains. # # @param audiences [Array<String>] coerced required audiences # @return [void] def ensure_resource_client!(audiences) client = resource_client.to_s error_msg = 'resource_client must match one of required_aud when using :resource_or_aud profile' if needs_resource_client_inference?(client, audiences) raise Verikloak::Audience::ConfigurationError, error_msg unless audiences.one? self.resource_client = audiences.first client = resource_client.to_s end return if audiences.include?(client) raise Verikloak::Audience::ConfigurationError, error_msg end # Decide whether the resource client should be inferred from the # required audiences based on the current client value. # # @param client [String] # @param audiences [Array<String>] # @return [Boolean] def needs_resource_client_inference?(client, audiences) client.empty? || (client == DEFAULT_RESOURCE_CLIENT && !audiences.include?(client)) end end |
#required_aud ⇒ Array<String,Symbol>, ...
Required audience(s). Can be a String/Symbol or an Array of them.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/verikloak/audience/configuration.rb', line 28 class Configuration DEFAULT_RESOURCE_CLIENT = 'rails-api' DEFAULT_ENV_CLAIMS_KEY = 'verikloak.user' DEFAULT_PROFILE = :strict_single # Enforcement profiles supported by {Verikloak::Audience::Checker}. VALID_PROFILES = %i[strict_single allow_account any_match resource_or_aud].freeze attr_accessor :profile, :required_aud, :resource_client, :suggest_in_logs, :skip_paths attr_reader :env_claims_key # Create a configuration with safe defaults. # # @return [void] def initialize @profile = DEFAULT_PROFILE @required_aud = [] @resource_client = DEFAULT_RESOURCE_CLIENT self.env_claims_key = DEFAULT_ENV_CLAIMS_KEY @suggest_in_logs = true @skip_paths = [] end # Ensure `dup` produces an independent copy. # # @param source [Configuration] # @return [void] def initialize_copy(source) super @profile = safe_dup(source.profile) @required_aud = duplicate_required_aud(source.required_aud) @resource_client = safe_dup(source.resource_client) self.env_claims_key = safe_dup(source.env_claims_key) @suggest_in_logs = source.suggest_in_logs @skip_paths = source.skip_paths&.dup || [] end # Coerce `required_aud` into an array of strings. # # @return [Array<String>] def required_aud_list Array(required_aud).map(&:to_s) end # Coerce `profile` into a Symbol, falling back to the default when unset. # Values that cannot be symbolized (e.g. an Integer) are returned as-is # so {#validated_profile} can reject them with a precise error. The # result is not guaranteed to be a member of {VALID_PROFILES}; use # {#validated_profile} to enforce that. # # @return [Symbol, Object] def normalized_profile value = profile value = value.to_sym if value.respond_to?(:to_sym) value.nil? ? DEFAULT_PROFILE : value end # Coerce `profile` into a Symbol and ensure it is one of # {VALID_PROFILES}. Single source of the unknown-profile check shared # by {#validate!} and {Verikloak::Audience::Checker.ok?}. # # @raise [ConfigurationError] when the profile is unknown # @return [Symbol] def validated_profile profile_name = normalized_profile unless VALID_PROFILES.include?(profile_name) raise Verikloak::Audience::ConfigurationError, "unknown audience profile #{profile.inspect}" end profile_name end # @param value [#to_s, nil] # @raise [ConfigurationError] when value is nil or blank # @return [void] def env_claims_key=(value) str = value&.to_s stripped = str&.strip if stripped.nil? || stripped.empty? raise Verikloak::Audience::ConfigurationError, 'env_claims_key must not be nil or empty' end @env_claims_key = stripped end # Validate the configuration to ensure required values are present. # # @raise [ConfigurationError] when `required_aud` is empty, `profile` is # not one of {VALID_PROFILES}, or `resource_client` conflicts with # `required_aud` under the `:resource_or_aud` profile # @return [Configuration] the validated configuration def validate! audiences = required_aud_list if audiences.empty? raise Verikloak::Audience::ConfigurationError, 'required_aud must include at least one audience' end profile_name = validated_profile ensure_resource_client!(audiences) if profile_name == :resource_or_aud self end private # Attempt to duplicate a value while tolerating non-duplicable inputs. # Returns `nil` when given nil and falls back to the original on duplication errors. # # @param value [Object, nil] # @return [Object, nil] def safe_dup(value) return if value.nil? value.dup rescue TypeError value end # Build a deep-ish copy of `required_aud` so that mutations on copies # do not leak back into the original configuration instance. # # @param value [Array<String,Symbol>, String, Symbol, nil] # @return [Array<String,Symbol>, String, Symbol, nil] def duplicate_required_aud(value) return if value.nil? return value.map { |item| safe_dup(item) } if value.is_a?(Array) safe_dup(value) end # Ensure that the configured `resource_client` fits the `required_aud` # list when the :resource_or_aud profile is active. Attempts to infer # the client id from `required_aud` when possible and raises when # ambiguity remains. # # @param audiences [Array<String>] coerced required audiences # @return [void] def ensure_resource_client!(audiences) client = resource_client.to_s error_msg = 'resource_client must match one of required_aud when using :resource_or_aud profile' if needs_resource_client_inference?(client, audiences) raise Verikloak::Audience::ConfigurationError, error_msg unless audiences.one? self.resource_client = audiences.first client = resource_client.to_s end return if audiences.include?(client) raise Verikloak::Audience::ConfigurationError, error_msg end # Decide whether the resource client should be inferred from the # required audiences based on the current client value. # # @param client [String] # @param audiences [Array<String>] # @return [Boolean] def needs_resource_client_inference?(client, audiences) client.empty? || (client == DEFAULT_RESOURCE_CLIENT && !audiences.include?(client)) end end |
#resource_client ⇒ String
Client id to use for resource_access[client].roles lookup.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/verikloak/audience/configuration.rb', line 28 class Configuration DEFAULT_RESOURCE_CLIENT = 'rails-api' DEFAULT_ENV_CLAIMS_KEY = 'verikloak.user' DEFAULT_PROFILE = :strict_single # Enforcement profiles supported by {Verikloak::Audience::Checker}. VALID_PROFILES = %i[strict_single allow_account any_match resource_or_aud].freeze attr_accessor :profile, :required_aud, :resource_client, :suggest_in_logs, :skip_paths attr_reader :env_claims_key # Create a configuration with safe defaults. # # @return [void] def initialize @profile = DEFAULT_PROFILE @required_aud = [] @resource_client = DEFAULT_RESOURCE_CLIENT self.env_claims_key = DEFAULT_ENV_CLAIMS_KEY @suggest_in_logs = true @skip_paths = [] end # Ensure `dup` produces an independent copy. # # @param source [Configuration] # @return [void] def initialize_copy(source) super @profile = safe_dup(source.profile) @required_aud = duplicate_required_aud(source.required_aud) @resource_client = safe_dup(source.resource_client) self.env_claims_key = safe_dup(source.env_claims_key) @suggest_in_logs = source.suggest_in_logs @skip_paths = source.skip_paths&.dup || [] end # Coerce `required_aud` into an array of strings. # # @return [Array<String>] def required_aud_list Array(required_aud).map(&:to_s) end # Coerce `profile` into a Symbol, falling back to the default when unset. # Values that cannot be symbolized (e.g. an Integer) are returned as-is # so {#validated_profile} can reject them with a precise error. The # result is not guaranteed to be a member of {VALID_PROFILES}; use # {#validated_profile} to enforce that. # # @return [Symbol, Object] def normalized_profile value = profile value = value.to_sym if value.respond_to?(:to_sym) value.nil? ? DEFAULT_PROFILE : value end # Coerce `profile` into a Symbol and ensure it is one of # {VALID_PROFILES}. Single source of the unknown-profile check shared # by {#validate!} and {Verikloak::Audience::Checker.ok?}. # # @raise [ConfigurationError] when the profile is unknown # @return [Symbol] def validated_profile profile_name = normalized_profile unless VALID_PROFILES.include?(profile_name) raise Verikloak::Audience::ConfigurationError, "unknown audience profile #{profile.inspect}" end profile_name end # @param value [#to_s, nil] # @raise [ConfigurationError] when value is nil or blank # @return [void] def env_claims_key=(value) str = value&.to_s stripped = str&.strip if stripped.nil? || stripped.empty? raise Verikloak::Audience::ConfigurationError, 'env_claims_key must not be nil or empty' end @env_claims_key = stripped end # Validate the configuration to ensure required values are present. # # @raise [ConfigurationError] when `required_aud` is empty, `profile` is # not one of {VALID_PROFILES}, or `resource_client` conflicts with # `required_aud` under the `:resource_or_aud` profile # @return [Configuration] the validated configuration def validate! audiences = required_aud_list if audiences.empty? raise Verikloak::Audience::ConfigurationError, 'required_aud must include at least one audience' end profile_name = validated_profile ensure_resource_client!(audiences) if profile_name == :resource_or_aud self end private # Attempt to duplicate a value while tolerating non-duplicable inputs. # Returns `nil` when given nil and falls back to the original on duplication errors. # # @param value [Object, nil] # @return [Object, nil] def safe_dup(value) return if value.nil? value.dup rescue TypeError value end # Build a deep-ish copy of `required_aud` so that mutations on copies # do not leak back into the original configuration instance. # # @param value [Array<String,Symbol>, String, Symbol, nil] # @return [Array<String,Symbol>, String, Symbol, nil] def duplicate_required_aud(value) return if value.nil? return value.map { |item| safe_dup(item) } if value.is_a?(Array) safe_dup(value) end # Ensure that the configured `resource_client` fits the `required_aud` # list when the :resource_or_aud profile is active. Attempts to infer # the client id from `required_aud` when possible and raises when # ambiguity remains. # # @param audiences [Array<String>] coerced required audiences # @return [void] def ensure_resource_client!(audiences) client = resource_client.to_s error_msg = 'resource_client must match one of required_aud when using :resource_or_aud profile' if needs_resource_client_inference?(client, audiences) raise Verikloak::Audience::ConfigurationError, error_msg unless audiences.one? self.resource_client = audiences.first client = resource_client.to_s end return if audiences.include?(client) raise Verikloak::Audience::ConfigurationError, error_msg end # Decide whether the resource client should be inferred from the # required audiences based on the current client value. # # @param client [String] # @param audiences [Array<String>] # @return [Boolean] def needs_resource_client_inference?(client, audiences) client.empty? || (client == DEFAULT_RESOURCE_CLIENT && !audiences.include?(client)) end end |
#skip_paths ⇒ Array<String, Regexp>
Paths to skip audience validation for (e.g., health checks). Synced from verikloak-rails when available.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/verikloak/audience/configuration.rb', line 28 class Configuration DEFAULT_RESOURCE_CLIENT = 'rails-api' DEFAULT_ENV_CLAIMS_KEY = 'verikloak.user' DEFAULT_PROFILE = :strict_single # Enforcement profiles supported by {Verikloak::Audience::Checker}. VALID_PROFILES = %i[strict_single allow_account any_match resource_or_aud].freeze attr_accessor :profile, :required_aud, :resource_client, :suggest_in_logs, :skip_paths attr_reader :env_claims_key # Create a configuration with safe defaults. # # @return [void] def initialize @profile = DEFAULT_PROFILE @required_aud = [] @resource_client = DEFAULT_RESOURCE_CLIENT self.env_claims_key = DEFAULT_ENV_CLAIMS_KEY @suggest_in_logs = true @skip_paths = [] end # Ensure `dup` produces an independent copy. # # @param source [Configuration] # @return [void] def initialize_copy(source) super @profile = safe_dup(source.profile) @required_aud = duplicate_required_aud(source.required_aud) @resource_client = safe_dup(source.resource_client) self.env_claims_key = safe_dup(source.env_claims_key) @suggest_in_logs = source.suggest_in_logs @skip_paths = source.skip_paths&.dup || [] end # Coerce `required_aud` into an array of strings. # # @return [Array<String>] def required_aud_list Array(required_aud).map(&:to_s) end # Coerce `profile` into a Symbol, falling back to the default when unset. # Values that cannot be symbolized (e.g. an Integer) are returned as-is # so {#validated_profile} can reject them with a precise error. The # result is not guaranteed to be a member of {VALID_PROFILES}; use # {#validated_profile} to enforce that. # # @return [Symbol, Object] def normalized_profile value = profile value = value.to_sym if value.respond_to?(:to_sym) value.nil? ? DEFAULT_PROFILE : value end # Coerce `profile` into a Symbol and ensure it is one of # {VALID_PROFILES}. Single source of the unknown-profile check shared # by {#validate!} and {Verikloak::Audience::Checker.ok?}. # # @raise [ConfigurationError] when the profile is unknown # @return [Symbol] def validated_profile profile_name = normalized_profile unless VALID_PROFILES.include?(profile_name) raise Verikloak::Audience::ConfigurationError, "unknown audience profile #{profile.inspect}" end profile_name end # @param value [#to_s, nil] # @raise [ConfigurationError] when value is nil or blank # @return [void] def env_claims_key=(value) str = value&.to_s stripped = str&.strip if stripped.nil? || stripped.empty? raise Verikloak::Audience::ConfigurationError, 'env_claims_key must not be nil or empty' end @env_claims_key = stripped end # Validate the configuration to ensure required values are present. # # @raise [ConfigurationError] when `required_aud` is empty, `profile` is # not one of {VALID_PROFILES}, or `resource_client` conflicts with # `required_aud` under the `:resource_or_aud` profile # @return [Configuration] the validated configuration def validate! audiences = required_aud_list if audiences.empty? raise Verikloak::Audience::ConfigurationError, 'required_aud must include at least one audience' end profile_name = validated_profile ensure_resource_client!(audiences) if profile_name == :resource_or_aud self end private # Attempt to duplicate a value while tolerating non-duplicable inputs. # Returns `nil` when given nil and falls back to the original on duplication errors. # # @param value [Object, nil] # @return [Object, nil] def safe_dup(value) return if value.nil? value.dup rescue TypeError value end # Build a deep-ish copy of `required_aud` so that mutations on copies # do not leak back into the original configuration instance. # # @param value [Array<String,Symbol>, String, Symbol, nil] # @return [Array<String,Symbol>, String, Symbol, nil] def duplicate_required_aud(value) return if value.nil? return value.map { |item| safe_dup(item) } if value.is_a?(Array) safe_dup(value) end # Ensure that the configured `resource_client` fits the `required_aud` # list when the :resource_or_aud profile is active. Attempts to infer # the client id from `required_aud` when possible and raises when # ambiguity remains. # # @param audiences [Array<String>] coerced required audiences # @return [void] def ensure_resource_client!(audiences) client = resource_client.to_s error_msg = 'resource_client must match one of required_aud when using :resource_or_aud profile' if needs_resource_client_inference?(client, audiences) raise Verikloak::Audience::ConfigurationError, error_msg unless audiences.one? self.resource_client = audiences.first client = resource_client.to_s end return if audiences.include?(client) raise Verikloak::Audience::ConfigurationError, error_msg end # Decide whether the resource client should be inferred from the # required audiences based on the current client value. # # @param client [String] # @param audiences [Array<String>] # @return [Boolean] def needs_resource_client_inference?(client, audiences) client.empty? || (client == DEFAULT_RESOURCE_CLIENT && !audiences.include?(client)) end end |
#suggest_in_logs ⇒ Boolean
Whether to log a suggestion when audience validation fails.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/verikloak/audience/configuration.rb', line 28 class Configuration DEFAULT_RESOURCE_CLIENT = 'rails-api' DEFAULT_ENV_CLAIMS_KEY = 'verikloak.user' DEFAULT_PROFILE = :strict_single # Enforcement profiles supported by {Verikloak::Audience::Checker}. VALID_PROFILES = %i[strict_single allow_account any_match resource_or_aud].freeze attr_accessor :profile, :required_aud, :resource_client, :suggest_in_logs, :skip_paths attr_reader :env_claims_key # Create a configuration with safe defaults. # # @return [void] def initialize @profile = DEFAULT_PROFILE @required_aud = [] @resource_client = DEFAULT_RESOURCE_CLIENT self.env_claims_key = DEFAULT_ENV_CLAIMS_KEY @suggest_in_logs = true @skip_paths = [] end # Ensure `dup` produces an independent copy. # # @param source [Configuration] # @return [void] def initialize_copy(source) super @profile = safe_dup(source.profile) @required_aud = duplicate_required_aud(source.required_aud) @resource_client = safe_dup(source.resource_client) self.env_claims_key = safe_dup(source.env_claims_key) @suggest_in_logs = source.suggest_in_logs @skip_paths = source.skip_paths&.dup || [] end # Coerce `required_aud` into an array of strings. # # @return [Array<String>] def required_aud_list Array(required_aud).map(&:to_s) end # Coerce `profile` into a Symbol, falling back to the default when unset. # Values that cannot be symbolized (e.g. an Integer) are returned as-is # so {#validated_profile} can reject them with a precise error. The # result is not guaranteed to be a member of {VALID_PROFILES}; use # {#validated_profile} to enforce that. # # @return [Symbol, Object] def normalized_profile value = profile value = value.to_sym if value.respond_to?(:to_sym) value.nil? ? DEFAULT_PROFILE : value end # Coerce `profile` into a Symbol and ensure it is one of # {VALID_PROFILES}. Single source of the unknown-profile check shared # by {#validate!} and {Verikloak::Audience::Checker.ok?}. # # @raise [ConfigurationError] when the profile is unknown # @return [Symbol] def validated_profile profile_name = normalized_profile unless VALID_PROFILES.include?(profile_name) raise Verikloak::Audience::ConfigurationError, "unknown audience profile #{profile.inspect}" end profile_name end # @param value [#to_s, nil] # @raise [ConfigurationError] when value is nil or blank # @return [void] def env_claims_key=(value) str = value&.to_s stripped = str&.strip if stripped.nil? || stripped.empty? raise Verikloak::Audience::ConfigurationError, 'env_claims_key must not be nil or empty' end @env_claims_key = stripped end # Validate the configuration to ensure required values are present. # # @raise [ConfigurationError] when `required_aud` is empty, `profile` is # not one of {VALID_PROFILES}, or `resource_client` conflicts with # `required_aud` under the `:resource_or_aud` profile # @return [Configuration] the validated configuration def validate! audiences = required_aud_list if audiences.empty? raise Verikloak::Audience::ConfigurationError, 'required_aud must include at least one audience' end profile_name = validated_profile ensure_resource_client!(audiences) if profile_name == :resource_or_aud self end private # Attempt to duplicate a value while tolerating non-duplicable inputs. # Returns `nil` when given nil and falls back to the original on duplication errors. # # @param value [Object, nil] # @return [Object, nil] def safe_dup(value) return if value.nil? value.dup rescue TypeError value end # Build a deep-ish copy of `required_aud` so that mutations on copies # do not leak back into the original configuration instance. # # @param value [Array<String,Symbol>, String, Symbol, nil] # @return [Array<String,Symbol>, String, Symbol, nil] def duplicate_required_aud(value) return if value.nil? return value.map { |item| safe_dup(item) } if value.is_a?(Array) safe_dup(value) end # Ensure that the configured `resource_client` fits the `required_aud` # list when the :resource_or_aud profile is active. Attempts to infer # the client id from `required_aud` when possible and raises when # ambiguity remains. # # @param audiences [Array<String>] coerced required audiences # @return [void] def ensure_resource_client!(audiences) client = resource_client.to_s error_msg = 'resource_client must match one of required_aud when using :resource_or_aud profile' if needs_resource_client_inference?(client, audiences) raise Verikloak::Audience::ConfigurationError, error_msg unless audiences.one? self.resource_client = audiences.first client = resource_client.to_s end return if audiences.include?(client) raise Verikloak::Audience::ConfigurationError, error_msg end # Decide whether the resource client should be inferred from the # required audiences based on the current client value. # # @param client [String] # @param audiences [Array<String>] # @return [Boolean] def needs_resource_client_inference?(client, audiences) client.empty? || (client == DEFAULT_RESOURCE_CLIENT && !audiences.include?(client)) end end |
Instance Method Details
#initialize_copy(source) ⇒ void
This method returns an undefined value.
Ensure dup produces an independent copy.
56 57 58 59 60 61 62 63 64 |
# File 'lib/verikloak/audience/configuration.rb', line 56 def initialize_copy(source) super @profile = safe_dup(source.profile) @required_aud = duplicate_required_aud(source.required_aud) @resource_client = safe_dup(source.resource_client) self.env_claims_key = safe_dup(source.env_claims_key) @suggest_in_logs = source.suggest_in_logs @skip_paths = source.skip_paths&.dup || [] end |
#normalized_profile ⇒ Symbol, Object
Coerce profile into a Symbol, falling back to the default when unset.
Values that cannot be symbolized (e.g. an Integer) are returned as-is
so #validated_profile can reject them with a precise error. The
result is not guaranteed to be a member of VALID_PROFILES; use
#validated_profile to enforce that.
80 81 82 83 84 |
# File 'lib/verikloak/audience/configuration.rb', line 80 def normalized_profile value = profile value = value.to_sym if value.respond_to?(:to_sym) value.nil? ? DEFAULT_PROFILE : value end |
#required_aud_list ⇒ Array<String>
Coerce required_aud into an array of strings.
69 70 71 |
# File 'lib/verikloak/audience/configuration.rb', line 69 def required_aud_list Array(required_aud).map(&:to_s) end |
#validate! ⇒ Configuration
Validate the configuration to ensure required values are present.
122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/verikloak/audience/configuration.rb', line 122 def validate! audiences = required_aud_list if audiences.empty? raise Verikloak::Audience::ConfigurationError, 'required_aud must include at least one audience' end profile_name = validated_profile ensure_resource_client!(audiences) if profile_name == :resource_or_aud self end |
#validated_profile ⇒ Symbol
Coerce profile into a Symbol and ensure it is one of
VALID_PROFILES. Single source of the unknown-profile check shared
by #validate! and Verikloak::Audience::Checker.ok?.
92 93 94 95 96 97 98 99 100 |
# File 'lib/verikloak/audience/configuration.rb', line 92 def validated_profile profile_name = normalized_profile unless VALID_PROFILES.include?(profile_name) raise Verikloak::Audience::ConfigurationError, "unknown audience profile #{profile.inspect}" end profile_name end |