Module: OmniauthOpenidFederation::Utils

Defined in:
lib/omniauth_openid_federation/utils.rb

Class Method Summary collapse

Class Method Details

.build_endpoint_url(issuer_uri, endpoint_path) ⇒ String

Build full endpoint URL from issuer and endpoint path

Parameters:

  • issuer_uri (String, URI)

    Issuer URI (e.g., “provider.example.com”)

  • endpoint_path (String)

    Endpoint path (e.g., “/oauth2/authorize”)

Returns:

  • (String)

    Full endpoint URL



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

Parameters:

  • issuer_uri (String, URI)

    Issuer URI (e.g., “provider.example.com”)

  • entity_statement_endpoint (String, nil) (defaults to: nil)

    Entity statement endpoint path (e.g., “/.well-known/openid-federation”)

Returns:

  • (String)

    Full entity statement URL



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

Parameters:

  • entity_statement_content (String)

    Entity statement JWT string

Returns:

  • (Hash, nil)

    JWKS hash with “keys” array, or nil if extraction fails



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

Parameters:

  • key (OpenSSL::PKey::RSA)

    RSA private or public key

  • use (String, nil) (defaults to: "sig")

    Key use (“sig” for signing, “enc” for encryption, nil for both)

Returns:

  • (Hash)

    JWK hash with kty, kid, n, e, and optionally use



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)

Parameters:

  • path (String, nil)

    The file path

Returns:

  • (String)

    Sanitized path (filename only)



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)

Parameters:

  • uri (String, nil)

    The URI

Returns:

  • (String)

    Sanitized URI



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

Parameters:

  • hash (Hash, Object)

    The hash to convert

Returns:

  • (Hash, HashWithIndifferentAccess)

    Converted hash



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)

Parameters:

  • str (String)

    The string to validate

Returns:

  • (Boolean)

    true if valid JWT format, false otherwise



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

Parameters:

  • path (String, Pathname)

    The file path to validate

  • allowed_dirs (Array<String>, nil) (defaults to: nil)

    Allowed base directories (optional)

Returns:

  • (String)

    Resolved absolute path

Raises:

  • (SecurityError)

    If path traversal is detected or path is outside allowed directories



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.expand_path(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|
      expanded_dir = File.expand_path(dir)
      resolved.start_with?(expanded_dir)
    end
    unless allowed
      raise SecurityError, "File path outside allowed directories: #{sanitize_path(path)}"
    end
  end

  resolved
end