Module: OmniauthOpenidFederation::Validators
- Defined in:
- lib/omniauth_openid_federation/validators.rb
Class Method Summary collapse
-
.normalize_acr_values(acr_values, max_length: nil, skip_sanitization: false) ⇒ String?
Validate and normalize acr_values parameter per OIDC Core 1.0 spec acr_values is a space-separated string of ACR values Security: Uses allowed characters approach - only allows printable ASCII characters.
-
.normalize_hash(hash) ⇒ Hash
Normalize hash keys to symbols.
-
.sanitize_request_param(value, max_length: nil, allow_control_chars: false) ⇒ Object
Validate and sanitize user input from HTTP requests only (not config values) Prevents URI exploitation, ReDoS, string overflow, and control character attacks Default max_length uses Configuration.config.max_string_length (8KB default) - large enough for legitimate use, prevents DoS attacks.
- .validate_allowed_acr_value!(acr_value, allowed_acr_values) ⇒ Object
-
.validate_client_id!(client_id) ⇒ String
Validate and sanitize client_id per OIDC Core 1.0 spec client_id is REQUIRED and must be a valid string.
-
.validate_client_options!(client_options) ⇒ Object
Validate client options hash (for configuration validation only) Note: This validates configuration structure, not security (config is trusted).
-
.validate_entity_identifier!(entity_id, max_length: nil) ⇒ String
Validate entity identifier per OpenID Federation 1.0 spec Entity identifiers are URIs that identify entities in the federation.
-
.validate_file_path!(path, required: false) ⇒ Object
Validate that a file path exists.
-
.validate_nonce(nonce, required: false) ⇒ String?
Validate and sanitize nonce parameter nonce is REQUIRED for Implicit and Hybrid flows, RECOMMENDED for Authorization Code flow.
-
.validate_private_key!(private_key, min_bits: Constants::MIN_RSA_KEY_BITS) ⇒ Object
Validate that a private key is present and valid.
-
.validate_redirect_uri!(redirect_uri) ⇒ String
Validate and sanitize redirect_uri per OIDC Core 1.0 spec redirect_uri is REQUIRED and must be a valid absolute URI.
- .validate_required_request_object_claims!(claims, required_names) ⇒ Object
-
.validate_response_type!(response_type) ⇒ String
Validate and sanitize response_type per OIDC Core 1.0 spec response_type is REQUIRED and must be a valid value.
-
.validate_scope!(scope, require_openid: true) ⇒ String
Validate and sanitize scope per OIDC Core 1.0 spec scope is space-delimited, case-sensitive list of ASCII string values MUST include "openid" scope value.
-
.validate_state!(state) ⇒ String
Validate and sanitize state parameter state is REQUIRED for CSRF protection.
-
.validate_uri!(uri, required: false) ⇒ Object
Validate that a URI is valid.
-
.validate_uri_safe!(uri_str, max_length: nil, allowed_schemes: ["http", "https"]) ⇒ Object
Validate URI for user input only (not config values) Prevents URI gem exploitation and validates scheme/length.
Class Method Details
.normalize_acr_values(acr_values, max_length: nil, skip_sanitization: false) ⇒ String?
Validate and normalize acr_values parameter per OIDC Core 1.0 spec acr_values is a space-separated string of ACR values Security: Uses allowed characters approach - only allows printable ASCII characters
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'lib/omniauth_openid_federation/validators.rb', line 225 def self.normalize_acr_values(acr_values, max_length: nil, skip_sanitization: false) max_length ||= ::OmniauthOpenidFederation::Configuration.config.max_string_length return nil if StringHelpers.blank?(acr_values) case acr_values when Array values = acr_values.map(&:to_s).map(&:strip).reject { |v| StringHelpers.blank?(v) } when String trimmed = acr_values.strip values = trimmed.split(" ").map(&:strip).reject { |v| StringHelpers.blank?(v) } else str = acr_values.to_s.strip return nil if str.length > max_length values = str.split(" ").map(&:strip).reject { |v| StringHelpers.blank?(v) } end unless skip_sanitization values = values.map { |v| sanitize_request_param(v) }.compact end return nil if values.empty? result = values.join(" ") return nil if result.length > max_length result end |
.normalize_hash(hash) ⇒ Hash
Normalize hash keys to symbols
158 159 160 161 162 163 164 165 |
# File 'lib/omniauth_openid_federation/validators.rb', line 158 def self.normalize_hash(hash) return {} if hash.nil? hash.each_with_object({}) do |(k, v), result| key = k.is_a?(String) ? k.to_sym : k result[key] = v end end |
.sanitize_request_param(value, max_length: nil, allow_control_chars: false) ⇒ Object
Validate and sanitize user input from HTTP requests only (not config values) Prevents URI exploitation, ReDoS, string overflow, and control character attacks Default max_length uses Configuration.config.max_string_length (8KB default) - large enough for legitimate use, prevents DoS attacks
170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/omniauth_openid_federation/validators.rb', line 170 def self.sanitize_request_param(value, max_length: nil, allow_control_chars: false) max_length ||= ::OmniauthOpenidFederation::Configuration.config.max_string_length return nil if value.nil? str = value.to_s.strip return nil if str.length > max_length unless allow_control_chars str = str.gsub(/[^\x20-\x7E]/, "") end str.empty? ? nil : str end |
.validate_allowed_acr_value!(acr_value, allowed_acr_values) ⇒ Object
52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/omniauth_openid_federation/validators.rb', line 52 def self.validate_allowed_acr_value!(acr_value, allowed_acr_values) allowed = Array(allowed_acr_values).map(&:to_s).map(&:strip).reject { |value| StringHelpers.blank?(value) } return true if allowed.empty? token_acr = acr_value.to_s.strip if StringHelpers.blank?(token_acr) || !allowed.include?(token_acr) raise ValidationError, "ID token acr mismatch: expected one of [#{allowed.join(", ")}], got '#{acr_value}'" end true end |
.validate_client_id!(client_id) ⇒ String
Validate and sanitize client_id per OIDC Core 1.0 spec client_id is REQUIRED and must be a valid string
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/omniauth_openid_federation/validators.rb', line 259 def self.validate_client_id!(client_id) if StringHelpers.blank?(client_id) raise ConfigurationError, "client_id is REQUIRED per OIDC Core 1.0 spec" end str = client_id.to_s.strip if str.empty? raise ConfigurationError, "client_id cannot be empty after trimming" end sanitized = sanitize_request_param(str) if StringHelpers.blank?(sanitized) raise ConfigurationError, "client_id contains invalid characters" end sanitized end |
.validate_client_options!(client_options) ⇒ Object
Validate client options hash (for configuration validation only) Note: This validates configuration structure, not security (config is trusted)
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 |
# File 'lib/omniauth_openid_federation/validators.rb', line 117 def self.() ||= {} normalized = normalize_hash() if StringHelpers.blank?(normalized[:identifier]) raise ConfigurationError, "Client identifier is required" end if StringHelpers.blank?(normalized[:redirect_uri]) raise ConfigurationError, "Redirect URI is required" end begin parsed = URI.parse(normalized[:redirect_uri].to_s) unless parsed.is_a?(URI::HTTP) || parsed.is_a?(URI::HTTPS) raise ConfigurationError, "Redirect URI must be HTTP or HTTPS: #{normalized[:redirect_uri]}" end rescue URI::InvalidURIError => e raise ConfigurationError, "Invalid redirect URI format: #{e.}" end validate_private_key!(normalized[:private_key]) %i[authorization_endpoint token_endpoint jwks_uri].each do |endpoint| if normalized.key?(endpoint) && !StringHelpers.blank?(normalized[endpoint]) # Endpoints can be paths or full URLs endpoint_value = normalized[endpoint] unless endpoint_value.to_s.start_with?("/", "http://", "https://") raise ConfigurationError, "Invalid endpoint format for #{endpoint}: #{endpoint_value}" end end end normalized end |
.validate_entity_identifier!(entity_id, max_length: nil) ⇒ String
Validate entity identifier per OpenID Federation 1.0 spec Entity identifiers are URIs that identify entities in the federation
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 |
# File 'lib/omniauth_openid_federation/validators.rb', line 431 def self.validate_entity_identifier!(entity_id, max_length: nil) max_length ||= ::OmniauthOpenidFederation::Configuration.config.max_string_length if StringHelpers.blank?(entity_id) raise SecurityError, "Entity identifier cannot be nil or empty" end str = entity_id.to_s.strip if str.empty? raise SecurityError, "Entity identifier cannot be empty after trimming" end validate_uri_safe!(str, max_length: max_length, allowed_schemes: ["http", "https"]) str end |
.validate_file_path!(path, required: false) ⇒ Object
Validate that a file path exists
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/omniauth_openid_federation/validators.rb', line 94 def self.validate_file_path!(path, required: false) if StringHelpers.blank?(path) if required raise ConfigurationError, "File path is required" end return false end unless File.exist?(path) if required raise ConfigurationError, "File not found: #{path}" end return false end true end |
.validate_nonce(nonce, required: false) ⇒ String?
Validate and sanitize nonce parameter nonce is REQUIRED for Implicit and Hybrid flows, RECOMMENDED for Authorization Code flow
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 |
# File 'lib/omniauth_openid_federation/validators.rb', line 372 def self.validate_nonce(nonce, required: false) return nil unless nonce str = nonce.to_s.strip if str.empty? if required raise ConfigurationError, "nonce is REQUIRED but is empty after trimming" end return nil end sanitized = sanitize_request_param(str) if StringHelpers.blank?(sanitized) if required raise ConfigurationError, "nonce contains invalid characters" end return nil end sanitized end |
.validate_private_key!(private_key, min_bits: Constants::MIN_RSA_KEY_BITS) ⇒ Object
Validate that a private key is present and valid
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/omniauth_openid_federation/validators.rb', line 12 def self.validate_private_key!(private_key, min_bits: Constants::MIN_RSA_KEY_BITS) if private_key.nil? raise ConfigurationError, "Private key is required for signed request objects" end normalized_key = if private_key.is_a?(String) begin OpenSSL::PKey::RSA.new(private_key) rescue => error raise ConfigurationError, "Invalid private key format: #{error.}" end elsif private_key.is_a?(OpenSSL::PKey::RSA) private_key else raise ConfigurationError, "Private key must be an OpenSSL::PKey::RSA instance or PEM string" end if min_bits.to_i.positive? && normalized_key.n.num_bits < min_bits.to_i raise ConfigurationError, "RSA private key must be at least #{min_bits} bits (got #{normalized_key.n.num_bits} bits)" end true end |
.validate_redirect_uri!(redirect_uri) ⇒ String
Validate and sanitize redirect_uri per OIDC Core 1.0 spec redirect_uri is REQUIRED and must be a valid absolute URI
283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/omniauth_openid_federation/validators.rb', line 283 def self.validate_redirect_uri!(redirect_uri) if StringHelpers.blank?(redirect_uri) raise ConfigurationError, "redirect_uri is REQUIRED per OIDC Core 1.0 spec" end str = redirect_uri.to_s.strip if str.empty? raise ConfigurationError, "redirect_uri cannot be empty after trimming" end validated = validate_uri_safe!(str, allowed_schemes: ["http", "https"]) validated.to_s end |
.validate_required_request_object_claims!(claims, required_names) ⇒ Object
38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/omniauth_openid_federation/validators.rb', line 38 def self.validate_required_request_object_claims!(claims, required_names) required = Array(required_names).map(&:to_s).uniq return true if required.empty? normalized_claims = (claims || {}).each_with_object({}) do |(key, value), hash| hash[key.to_s] = value end missing = required.reject { |name| StringHelpers.present?(normalized_claims[name]) } return true if missing.empty? raise ConfigurationError, "Missing required request object claims: #{missing.join(", ")}" end |
.validate_response_type!(response_type) ⇒ String
Validate and sanitize response_type per OIDC Core 1.0 spec response_type is REQUIRED and must be a valid value
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 |
# File 'lib/omniauth_openid_federation/validators.rb', line 400 def self.validate_response_type!(response_type) if StringHelpers.blank?(response_type) raise ConfigurationError, "response_type is REQUIRED per OIDC Core 1.0 spec" end str = response_type.to_s.strip if str.empty? raise ConfigurationError, "response_type cannot be empty after trimming" end sanitized = sanitize_request_param(str) if StringHelpers.blank?(sanitized) raise ConfigurationError, "response_type contains invalid characters" end valid_types = ["code", "id_token", "token", "id_token token", "code id_token", "code token", "code id_token token"] types = sanitized.split(" ").map(&:strip) unless types.all? { |t| valid_types.include?(t) || t.match?(/^[a-z_]+$/) } raise ConfigurationError, "response_type contains invalid value: #{sanitized}" end sanitized end |
.validate_scope!(scope, require_openid: true) ⇒ String
Validate and sanitize scope per OIDC Core 1.0 spec scope is space-delimited, case-sensitive list of ASCII string values MUST include "openid" scope value
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/omniauth_openid_federation/validators.rb', line 305 def self.validate_scope!(scope, require_openid: true) if StringHelpers.blank?(scope) if require_openid raise ConfigurationError, "scope is REQUIRED and MUST include 'openid' per OIDC Core 1.0 spec" end return nil end scopes = case scope when Array scope.map(&:to_s).map(&:strip).reject { |s| StringHelpers.blank?(s) } when String scope.strip.split(" ").map(&:strip).reject { |s| StringHelpers.blank?(s) } else scope.to_s.strip.split(" ").map(&:strip).reject { |s| StringHelpers.blank?(s) } end scopes = scopes.map { |s| sanitize_request_param(s) }.compact if scopes.empty? raise ConfigurationError, "scope cannot be empty after validation" end if require_openid && !scopes.include?("openid") raise ConfigurationError, "scope MUST include 'openid' per OIDC Core 1.0 spec" end result = scopes.join(" ") max_length = ::OmniauthOpenidFederation::Configuration.config.max_string_length if result.length > max_length raise ConfigurationError, "scope exceeds maximum length of #{max_length} characters" end result end |
.validate_state!(state) ⇒ String
Validate and sanitize state parameter state is REQUIRED for CSRF protection
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 |
# File 'lib/omniauth_openid_federation/validators.rb', line 347 def self.validate_state!(state) if StringHelpers.blank?(state) raise ConfigurationError, "state is REQUIRED for CSRF protection" end str = state.to_s.strip if str.empty? raise ConfigurationError, "state cannot be empty after trimming" end sanitized = sanitize_request_param(str) if StringHelpers.blank?(sanitized) raise ConfigurationError, "state contains invalid characters" end sanitized end |
.validate_uri!(uri, required: false) ⇒ Object
Validate that a URI is valid
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/omniauth_openid_federation/validators.rb', line 70 def self.validate_uri!(uri, required: false) if StringHelpers.blank?(uri) if required raise ConfigurationError, "URI is required" end return false end begin parsed = URI.parse(uri.to_s) unless parsed.is_a?(URI::HTTP) || parsed.is_a?(URI::HTTPS) raise ConfigurationError, "URI must be HTTP or HTTPS: #{uri}" end true rescue URI::InvalidURIError => e raise ConfigurationError, "Invalid URI format: #{e.}" end end |
.validate_uri_safe!(uri_str, max_length: nil, allowed_schemes: ["http", "https"]) ⇒ Object
Validate URI for user input only (not config values) Prevents URI gem exploitation and validates scheme/length
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/omniauth_openid_federation/validators.rb', line 186 def self.validate_uri_safe!(uri_str, max_length: nil, allowed_schemes: ["http", "https"]) max_length ||= ::OmniauthOpenidFederation::Configuration.config.max_string_length raise SecurityError, "URI cannot be nil" if uri_str.nil? original_str = uri_str.to_s sanitized = original_str.gsub(/[^\x20-\x7E]/, "") raise SecurityError, "URI contains invalid characters (only printable ASCII allowed)" if sanitized != original_str str = sanitized.strip raise SecurityError, "URI cannot be empty" if str.empty? raise SecurityError, "URI exceeds maximum length of #{max_length} characters" if str.length > max_length begin parsed = URI.parse(str) rescue URI::InvalidURIError => e raise SecurityError, "Invalid URI format: #{e.}" end unless parsed.scheme && allowed_schemes.include?(parsed.scheme.downcase) raise SecurityError, "URI scheme must be one of: #{allowed_schemes.join(", ")}" end unless parsed.is_a?(URI::HTTP) || parsed.is_a?(URI::HTTPS) raise SecurityError, "URI must be HTTP or HTTPS" end raise SecurityError, "URI host cannot be empty" if StringHelpers.blank?(parsed.host) parsed end |