Module: OmniauthOpenidFederation::Utils
- Defined in:
- lib/omniauth_openid_federation/utils.rb
Class Method Summary collapse
-
.build_endpoint_url(issuer_uri, endpoint_path) ⇒ String
Build full endpoint URL from issuer and endpoint path.
-
.build_entity_statement_url(issuer_uri, entity_statement_endpoint: nil) ⇒ String
Build full entity statement URL from issuer and endpoint path.
-
.extract_jwks_from_entity_statement(entity_statement_content) ⇒ Hash?
Extract JWKS from entity statement JWT.
-
.rsa_key_to_jwk(key, use: "sig") ⇒ Hash
Convert RSA key to JWK format.
-
.sanitize_path(path) ⇒ String
Sanitize file path for error messages (only show filename, not full path).
-
.sanitize_uri(uri) ⇒ String
Sanitize URI for error messages (only show scheme and host).
-
.to_indifferent_hash(hash) ⇒ Hash, HashWithIndifferentAccess
Convert hash to HashWithIndifferentAccess if available.
-
.valid_jwt_format?(str) ⇒ Boolean
Validate JWT format (must have exactly 3 parts separated by dots).
-
.validate_file_path!(path, allowed_dirs: nil) ⇒ String
Validate file path to prevent path traversal attacks.
Class Method Details
.build_endpoint_url(issuer_uri, endpoint_path) ⇒ String
Build full endpoint URL from issuer and endpoint path
46 47 48 49 50 51 52 53 54 |
# File 'lib/omniauth_openid_federation/utils.rb', line 46 def self.build_endpoint_url(issuer_uri, endpoint_path) return endpoint_path if endpoint_path.to_s.start_with?("http://", "https://") issuer_str = issuer_uri.to_s issuer_str = issuer_str.chomp("/") path = endpoint_path.to_s path = "/#{path}" unless path.start_with?("/") "#{issuer_str}#{path}" end |
.build_entity_statement_url(issuer_uri, entity_statement_endpoint: nil) ⇒ String
Build full entity statement URL from issuer and endpoint path
61 62 63 64 |
# File 'lib/omniauth_openid_federation/utils.rb', line 61 def self.build_entity_statement_url(issuer_uri, entity_statement_endpoint: nil) endpoint = entity_statement_endpoint || "/.well-known/openid-federation" build_endpoint_url(issuer_uri, endpoint) end |
.extract_jwks_from_entity_statement(entity_statement_content) ⇒ Hash?
Extract JWKS from entity statement JWT
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/omniauth_openid_federation/utils.rb', line 141 def self.extract_jwks_from_entity_statement(entity_statement_content) require "json" require "base64" return nil unless valid_jwt_format?(entity_statement_content) jwt_parts = entity_statement_content.split(".") return nil unless jwt_parts.length == 3 begin payload = JSON.parse(Base64.urlsafe_decode64(jwt_parts[1])) entity_jwks = payload["jwks"] || payload[:jwks] || {} return nil if entity_jwks.empty? keys = entity_jwks["keys"] || entity_jwks[:keys] || [] return nil if keys.empty? {keys: Array(keys)} rescue => e OmniauthOpenidFederation::Logger.warn("[Utils] Failed to extract JWKS from entity statement: #{e.message}") nil end end |
.rsa_key_to_jwk(key, use: "sig") ⇒ Hash
Convert RSA key to JWK format
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/omniauth_openid_federation/utils.rb', line 115 def self.rsa_key_to_jwk(key, use: "sig") require "digest" require "base64" n = Base64.urlsafe_encode64(key.n.to_s(2), padding: false) e = Base64.urlsafe_encode64(key.e.to_s(2), padding: false) public_key_pem = key.public_key.to_pem kid = Digest::SHA256.hexdigest(public_key_pem)[0, 16] jwk = { kty: "RSA", kid: kid, n: n, e: e } jwk[:use] = use if use jwk end |
.sanitize_path(path) ⇒ String
Sanitize file path for error messages (only show filename, not full path)
22 23 24 25 |
# File 'lib/omniauth_openid_federation/utils.rb', line 22 def self.sanitize_path(path) return "[REDACTED]" if StringHelpers.blank?(path) File.basename(path) end |
.sanitize_uri(uri) ⇒ String
Sanitize URI for error messages (only show scheme and host)
31 32 33 34 35 36 37 38 39 |
# File 'lib/omniauth_openid_federation/utils.rb', line 31 def self.sanitize_uri(uri) return "[REDACTED]" if StringHelpers.blank?(uri) begin parsed = URI.parse(uri) "#{parsed.scheme}://#{parsed.host}/[REDACTED]" rescue URI::InvalidURIError "[REDACTED]" end end |
.to_indifferent_hash(hash) ⇒ Hash, HashWithIndifferentAccess
Convert hash to HashWithIndifferentAccess if available
10 11 12 13 14 15 16 |
# File 'lib/omniauth_openid_federation/utils.rb', line 10 def self.to_indifferent_hash(hash) if defined?(ActiveSupport::HashWithIndifferentAccess) ActiveSupport::HashWithIndifferentAccess.new(hash) else hash.is_a?(Hash) ? hash : hash.to_h end end |
.valid_jwt_format?(str) ⇒ Boolean
Validate JWT format (must have exactly 3 parts separated by dots)
104 105 106 107 108 |
# File 'lib/omniauth_openid_federation/utils.rb', line 104 def self.valid_jwt_format?(str) return false unless str.is_a?(String) parts = str.split(".") parts.length == 3 && parts.all? { |p| p.length > 0 } end |
.validate_file_path!(path, allowed_dirs: nil) ⇒ String
Validate file path to prevent path traversal attacks
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 |
# File 'lib/omniauth_openid_federation/utils.rb', line 72 def self.validate_file_path!(path, allowed_dirs: nil) raise SecurityError, "File path cannot be nil" if path.nil? path_str = path.to_s raise SecurityError, "File path cannot be empty" if path_str.empty? if path_str.include?("..") || path_str.include?("~") raise SecurityError, "Path traversal detected in: #{sanitize_path(path_str)}" end resolved = File.(path_str) # Validate it's within allowed directories if specified # When allowed_dirs is nil, we trust the developer to pass appropriate paths # Path traversal protection (.. and ~) is still enforced above if allowed_dirs && !allowed_dirs.empty? allowed = allowed_dirs.any? do |dir| = File.(dir) resolved.start_with?() end unless allowed raise SecurityError, "File path outside allowed directories: #{sanitize_path(path)}" end end resolved end |