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_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) ⇒ 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_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
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 216 |
# File 'lib/omniauth_openid_federation/validators.rb', line 190 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
123 124 125 126 127 128 129 130 |
# File 'lib/omniauth_openid_federation/validators.rb', line 123 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
135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/omniauth_openid_federation/validators.rb', line 135 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_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
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/omniauth_openid_federation/validators.rb', line 224 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)
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 |
# File 'lib/omniauth_openid_federation/validators.rb', line 82 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.message}" end validate_private_key!(normalized[:private_key]) i[ 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
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 |
# File 'lib/omniauth_openid_federation/validators.rb', line 396 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
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/omniauth_openid_federation/validators.rb', line 59 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
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 |
# File 'lib/omniauth_openid_federation/validators.rb', line 337 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) ⇒ 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 |
# File 'lib/omniauth_openid_federation/validators.rb', line 12 def self.validate_private_key!(private_key) if private_key.nil? raise ConfigurationError, "Private key is required for signed request objects" end if private_key.is_a?(String) begin OpenSSL::PKey::RSA.new(private_key) rescue => e raise ConfigurationError, "Invalid private key format: #{e.message}" end elsif !private_key.is_a?(OpenSSL::PKey::RSA) raise ConfigurationError, "Private key must be an OpenSSL::PKey::RSA instance or PEM string" 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
248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/omniauth_openid_federation/validators.rb', line 248 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_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
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
# File 'lib/omniauth_openid_federation/validators.rb', line 365 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
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/omniauth_openid_federation/validators.rb', line 270 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
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/omniauth_openid_federation/validators.rb', line 312 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
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/omniauth_openid_federation/validators.rb', line 35 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.message}" 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
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 |
# File 'lib/omniauth_openid_federation/validators.rb', line 151 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.message}" 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 |