omniauth_openid_federation 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +1 -1
- data/app/controllers/omniauth_openid_federation/federation_controller.rb +1 -1
- data/config/routes.rb +20 -10
- data/examples/integration_test_flow.rb +4 -4
- data/examples/mock_op_server.rb +3 -3
- data/examples/mock_rp_server.rb +3 -3
- data/lib/omniauth_openid_federation/entity_statement_reader.rb +39 -14
- data/lib/omniauth_openid_federation/federation/entity_statement_builder.rb +7 -14
- data/lib/omniauth_openid_federation/federation/entity_statement_helper.rb +40 -11
- data/lib/omniauth_openid_federation/federation/entity_statement_validator.rb +6 -87
- data/lib/omniauth_openid_federation/federation/trust_chain_resolver.rb +3 -15
- data/lib/omniauth_openid_federation/federation_endpoint.rb +39 -171
- data/lib/omniauth_openid_federation/jwks/rotate.rb +45 -20
- data/lib/omniauth_openid_federation/jws.rb +2 -1
- data/lib/omniauth_openid_federation/rack_endpoint.rb +19 -7
- data/lib/omniauth_openid_federation/tasks_helper.rb +23 -5
- data/lib/omniauth_openid_federation/time_helpers.rb +60 -0
- data/lib/omniauth_openid_federation/utils.rb +4 -7
- data/lib/omniauth_openid_federation/validators.rb +12 -36
- data/lib/omniauth_openid_federation/version.rb +1 -1
- data/lib/omniauth_openid_federation.rb +1 -0
- data/lib/tasks/omniauth_openid_federation.rake +4 -3
- data/sig/omniauth_openid_federation.rbs +6 -0
- metadata +100 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8f8bba8b102bcf8c24fdd775d5d898d92b27c27d8e6df40c63ff23756aee4f8d
|
|
4
|
+
data.tar.gz: 900a1851f0a9917b5593f6146d965271180a979d312b9ff5fa15c295578172e5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 31a7bcc8b8e1661dc9bcc13604e2e92279d81501e0a1da25b8b48a6f9bf35251489720aa1879b6beeb89de32bd2970d72c086e58b1be43d0f4c125809339fb51
|
|
7
|
+
data.tar.gz: 5c27ea824e18a49a0fe899f6f6f2ade3c6a10872ade9b2eca3690cada27e06aa5ada813f1cdf34763881bc8c4ee5d1033f0a7ed821a8378602ee72db169150f7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 1.3.2 (2025-12-09)
|
|
4
|
+
|
|
5
|
+
- Added `TimeHelpers` module for compatibility with non-Rails environments
|
|
6
|
+
- Replaced `Time.zone` usage with `TimeHelpers` to work with or without ActiveSupport
|
|
7
|
+
|
|
8
|
+
## 1.3.1 (2025-12-09)
|
|
9
|
+
|
|
10
|
+
- Enhanced SSL configuration for HTTPS requests in tasks_helper.rb
|
|
11
|
+
- Updated federation controller to use ApplicationController
|
|
12
|
+
- Updated routes to have semaphore if it is already loaded
|
|
13
|
+
- Updated gemfiles and workflows for Rails 8 compatibility
|
|
14
|
+
- Improved time handling in integration and mock server classes using Time.zone.now
|
|
15
|
+
|
|
3
16
|
## 1.3.0 (2025-11-28)
|
|
4
17
|
|
|
5
18
|
- Added `prepare_request_object_params` proc option to customize request parameters before signing
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# omniauth_openid_federation
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/rb/omniauth_openid_federation) [](https://github.com/amkisko/omniauth_openid_federation.rb/actions/workflows/test.yml) [](https://codecov.io/gh/amkisko/omniauth_openid_federation.rb/
|
|
3
|
+
[](https://badge.fury.io/rb/omniauth_openid_federation) [](https://github.com/amkisko/omniauth_openid_federation.rb/actions/workflows/test.yml) [](https://codecov.io/gh/amkisko/omniauth_openid_federation.rb) [](https://sonarcloud.io/summary/new_code?id=amkisko_omniauth_openid_federation.rb)
|
|
4
4
|
|
|
5
5
|
OmniAuth strategy for OpenID Federation providers with comprehensive security features, supporting signed request objects, ID token encryption, and full OpenID Federation 1.0 compliance.
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
require "omniauth_openid_federation/cache_adapter"
|
|
13
13
|
|
|
14
14
|
module OmniauthOpenidFederation
|
|
15
|
-
class FederationController <
|
|
15
|
+
class FederationController < ::ApplicationController
|
|
16
16
|
# Serve the entity statement
|
|
17
17
|
#
|
|
18
18
|
# GET /.well-known/openid-federation
|
data/config/routes.rb
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
# Routes for OpenID Federation well-known endpoints
|
|
2
2
|
# These routes are mounted at the root level (not namespaced) because
|
|
3
3
|
# OpenID Federation spec requires specific well-known paths
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
# Guard to prevent double-loading routes (important for test isolation)
|
|
5
|
+
# Use a file-level instance variable that doesn't trigger class loading
|
|
6
|
+
@_omniauth_openid_federation_routes_loaded ||= false
|
|
7
|
+
unless @_omniauth_openid_federation_routes_loaded
|
|
8
|
+
@_omniauth_openid_federation_routes_loaded = true
|
|
9
|
+
begin
|
|
10
|
+
OmniauthOpenidFederation::Engine.routes.draw do
|
|
11
|
+
# OpenID Federation 1.0 Section 9: Entity Configuration endpoint
|
|
12
|
+
# MUST be at /.well-known/openid-federation
|
|
13
|
+
get "/.well-known/openid-federation", to: "omniauth_openid_federation/federation#show", as: :openid_federation
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
|
|
15
|
+
# Fetch endpoint for Subordinate Statements (Section 6.1)
|
|
16
|
+
get "/.well-known/openid-federation/fetch", to: "omniauth_openid_federation/federation#fetch", as: :openid_federation_fetch
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
# Standard JWKS endpoint
|
|
19
|
+
get "/.well-known/jwks.json", to: "omniauth_openid_federation/federation#jwks", as: :openid_federation_jwks
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
# Signed JWKS endpoint (OpenID Federation requirement)
|
|
22
|
+
get "/.well-known/signed-jwks.json", to: "omniauth_openid_federation/federation#signed_jwks", as: :openid_federation_signed_jwks
|
|
23
|
+
end
|
|
24
|
+
rescue NameError, LoadError
|
|
25
|
+
# Rails not available or not fully initialized - routes will be loaded when Engine initializes
|
|
26
|
+
end
|
|
17
27
|
end
|
|
@@ -319,7 +319,7 @@ class IntegrationTestFlow
|
|
|
319
319
|
attempt = 0
|
|
320
320
|
ready = false
|
|
321
321
|
server_name = url.include?("9292") ? "OP" : "RP"
|
|
322
|
-
start_time = Time.now
|
|
322
|
+
start_time = Time.zone.now
|
|
323
323
|
|
|
324
324
|
while attempt < max_attempts && !ready
|
|
325
325
|
begin
|
|
@@ -330,7 +330,7 @@ class IntegrationTestFlow
|
|
|
330
330
|
response = http.get(uri.path)
|
|
331
331
|
|
|
332
332
|
if response.code == "200"
|
|
333
|
-
elapsed = (Time.now - start_time).round(1)
|
|
333
|
+
elapsed = (Time.zone.now - start_time).round(1)
|
|
334
334
|
puts " ✓ #{url} is ready (#{elapsed}s)"
|
|
335
335
|
ready = true
|
|
336
336
|
end
|
|
@@ -342,7 +342,7 @@ class IntegrationTestFlow
|
|
|
342
342
|
attempt += 1
|
|
343
343
|
# Show progress every 5 attempts (1 second)
|
|
344
344
|
if attempt % 5 == 0
|
|
345
|
-
elapsed = (Time.now - start_time).round(1)
|
|
345
|
+
elapsed = (Time.zone.now - start_time).round(1)
|
|
346
346
|
print "."
|
|
347
347
|
end
|
|
348
348
|
sleep check_interval
|
|
@@ -350,7 +350,7 @@ class IntegrationTestFlow
|
|
|
350
350
|
end
|
|
351
351
|
|
|
352
352
|
unless ready
|
|
353
|
-
elapsed = (Time.now - start_time).round(1)
|
|
353
|
+
elapsed = (Time.zone.now - start_time).round(1)
|
|
354
354
|
puts "\n ✗ #{server_name} server at #{url} did not become ready in time (#{elapsed}s)"
|
|
355
355
|
err_log = File.join(@tmp_dir, "#{server_name.downcase}_server_error.log")
|
|
356
356
|
log_file = File.join(@tmp_dir, "#{server_name.downcase}_server.log")
|
data/examples/mock_op_server.rb
CHANGED
|
@@ -111,7 +111,7 @@ class MockOPServer
|
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
def self.load_signing_key(key_data)
|
|
114
|
-
if key_data.
|
|
114
|
+
if key_data.blank?
|
|
115
115
|
# Generate a new key for testing
|
|
116
116
|
OpenSSL::PKey::RSA.new(2048)
|
|
117
117
|
elsif key_data.is_a?(String)
|
|
@@ -126,7 +126,7 @@ class MockOPServer
|
|
|
126
126
|
end
|
|
127
127
|
|
|
128
128
|
def self.load_encryption_key(key_data)
|
|
129
|
-
return nil if key_data.
|
|
129
|
+
return nil if key_data.blank?
|
|
130
130
|
load_signing_key(key_data)
|
|
131
131
|
end
|
|
132
132
|
|
|
@@ -478,7 +478,7 @@ class MockOPServer
|
|
|
478
478
|
redirect_uri: redirect_uri,
|
|
479
479
|
state: state,
|
|
480
480
|
nonce: nonce,
|
|
481
|
-
created_at: Time.now
|
|
481
|
+
created_at: Time.zone.now
|
|
482
482
|
}
|
|
483
483
|
|
|
484
484
|
# Redirect back to RP with authorization code
|
data/examples/mock_rp_server.rb
CHANGED
|
@@ -66,7 +66,7 @@ class MockRPServer
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def self.load_signing_key(key_data)
|
|
69
|
-
if key_data.
|
|
69
|
+
if key_data.blank?
|
|
70
70
|
OpenSSL::PKey::RSA.new(2048)
|
|
71
71
|
elsif key_data.is_a?(String)
|
|
72
72
|
if key_data.include?("BEGIN")
|
|
@@ -80,7 +80,7 @@ class MockRPServer
|
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def self.load_encryption_key(key_data)
|
|
83
|
-
return nil if key_data.
|
|
83
|
+
return nil if key_data.blank?
|
|
84
84
|
load_signing_key(key_data)
|
|
85
85
|
end
|
|
86
86
|
|
|
@@ -297,7 +297,7 @@ class MockRPServer
|
|
|
297
297
|
provider_entity_id: provider_entity_id,
|
|
298
298
|
redirect_uri: redirect_uri,
|
|
299
299
|
nonce: nonce,
|
|
300
|
-
created_at: Time.now
|
|
300
|
+
created_at: Time.zone.now
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
# Step 5: Redirect to provider
|
|
@@ -5,6 +5,7 @@ require_relative "key_extractor"
|
|
|
5
5
|
require_relative "utils"
|
|
6
6
|
require_relative "configuration"
|
|
7
7
|
require_relative "logger"
|
|
8
|
+
require_relative "string_helpers"
|
|
8
9
|
|
|
9
10
|
# Entity Statement Reader for OpenID Federation 1.0
|
|
10
11
|
# @see https://openid.net/specs/openid-federation-1_0.html OpenID Federation 1.0 Specification
|
|
@@ -30,7 +31,7 @@ module OmniauthOpenidFederation
|
|
|
30
31
|
# @return [Array<Hash>] Array of JWK hash objects
|
|
31
32
|
def fetch_keys(entity_statement_path: nil)
|
|
32
33
|
entity_statement = load_entity_statement(entity_statement_path)
|
|
33
|
-
return [] if
|
|
34
|
+
return [] if StringHelpers.blank?(entity_statement)
|
|
34
35
|
|
|
35
36
|
# Decode self-signed entity statement
|
|
36
37
|
# Entity statements are self-signed, so we validate using their own JWKS
|
|
@@ -53,7 +54,7 @@ module OmniauthOpenidFederation
|
|
|
53
54
|
# @return [Hash, nil] Hash with provider metadata or nil if not found
|
|
54
55
|
def parse_metadata(entity_statement_path: nil)
|
|
55
56
|
entity_statement = load_entity_statement(entity_statement_path)
|
|
56
|
-
return nil if
|
|
57
|
+
return nil if StringHelpers.blank?(entity_statement)
|
|
57
58
|
|
|
58
59
|
# Decode JWT payload
|
|
59
60
|
jwt_parts = entity_statement.split(".")
|
|
@@ -93,21 +94,45 @@ module OmniauthOpenidFederation
|
|
|
93
94
|
def load_entity_statement(entity_statement_path)
|
|
94
95
|
return nil if entity_statement_path.nil? || entity_statement_path.to_s.empty?
|
|
95
96
|
|
|
96
|
-
#
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
# If path is absolute and exists, allow it (for temp files in tests)
|
|
98
|
+
# For absolute paths that don't exist, validate path traversal but allow outside allowed_dirs
|
|
99
|
+
# For relative paths, validate against allowed directories
|
|
100
|
+
path_str = entity_statement_path.to_s
|
|
101
|
+
is_absolute = path_str.start_with?("/", "~")
|
|
102
|
+
|
|
103
|
+
if is_absolute && File.exist?(entity_statement_path)
|
|
104
|
+
validated_path = entity_statement_path
|
|
105
|
+
elsif is_absolute
|
|
106
|
+
# Absolute path - validate path traversal but allow outside allowed_dirs
|
|
107
|
+
begin
|
|
108
|
+
validated_path = Utils.validate_file_path!(
|
|
109
|
+
entity_statement_path,
|
|
110
|
+
allowed_dirs: nil # Allow absolute paths outside config directory
|
|
111
|
+
)
|
|
112
|
+
rescue SecurityError
|
|
113
|
+
return nil
|
|
114
|
+
end
|
|
115
|
+
else
|
|
116
|
+
# Relative path - must be in allowed directories
|
|
117
|
+
config = OmniauthOpenidFederation::Configuration.config
|
|
118
|
+
allowed_dirs = if defined?(Rails) && Rails.root
|
|
119
|
+
[Rails.root.join("config").to_s]
|
|
120
|
+
elsif config.root_path
|
|
121
|
+
[File.join(config.root_path, "config")]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
begin
|
|
125
|
+
# Validate file path to prevent path traversal attacks
|
|
126
|
+
validated_path = Utils.validate_file_path!(
|
|
127
|
+
entity_statement_path,
|
|
128
|
+
allowed_dirs: allowed_dirs
|
|
129
|
+
)
|
|
130
|
+
rescue SecurityError
|
|
131
|
+
return nil
|
|
132
|
+
end
|
|
102
133
|
end
|
|
103
134
|
|
|
104
135
|
begin
|
|
105
|
-
# Validate file path to prevent path traversal attacks
|
|
106
|
-
validated_path = Utils.validate_file_path!(
|
|
107
|
-
entity_statement_path,
|
|
108
|
-
allowed_dirs: allowed_dirs
|
|
109
|
-
)
|
|
110
|
-
|
|
111
136
|
return nil unless File.exist?(validated_path)
|
|
112
137
|
|
|
113
138
|
File.read(validated_path)
|
|
@@ -4,6 +4,7 @@ require "openssl"
|
|
|
4
4
|
require "time"
|
|
5
5
|
require_relative "../logger"
|
|
6
6
|
require_relative "../errors"
|
|
7
|
+
require_relative "../string_helpers"
|
|
7
8
|
|
|
8
9
|
# Entity Statement Builder for OpenID Federation 1.0
|
|
9
10
|
# @see https://openid.net/specs/openid-federation-1_0.html OpenID Federation 1.0 Specification
|
|
@@ -82,7 +83,6 @@ module OmniauthOpenidFederation
|
|
|
82
83
|
|
|
83
84
|
payload = build_payload
|
|
84
85
|
|
|
85
|
-
# Build JWT header
|
|
86
86
|
# Per OpenID Federation 1.0 Section 3.1: typ MUST be "entity-statement+jwt"
|
|
87
87
|
header = {
|
|
88
88
|
alg: "RS256",
|
|
@@ -102,13 +102,13 @@ module OmniauthOpenidFederation
|
|
|
102
102
|
private
|
|
103
103
|
|
|
104
104
|
def validate_parameters
|
|
105
|
-
raise ConfigurationError, "Issuer is required" if
|
|
106
|
-
raise ConfigurationError, "Subject is required" if
|
|
105
|
+
raise ConfigurationError, "Issuer is required" if StringHelpers.blank?(@issuer)
|
|
106
|
+
raise ConfigurationError, "Subject is required" if StringHelpers.blank?(@subject)
|
|
107
107
|
raise ConfigurationError, "Private key is required" if @private_key.nil?
|
|
108
|
-
raise ConfigurationError, "JWKS is required" if
|
|
109
|
-
raise ConfigurationError, "Metadata is required" if
|
|
110
|
-
raise ConfigurationError, "JWKS must contain at least one key" if
|
|
111
|
-
raise ConfigurationError, "Key ID (kid) is required" if
|
|
108
|
+
raise ConfigurationError, "JWKS is required" if StringHelpers.blank?(@jwks)
|
|
109
|
+
raise ConfigurationError, "Metadata is required" if StringHelpers.blank?(@metadata)
|
|
110
|
+
raise ConfigurationError, "JWKS must contain at least one key" if StringHelpers.blank?(@jwks["keys"])
|
|
111
|
+
raise ConfigurationError, "Key ID (kid) is required" if StringHelpers.blank?(@kid)
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def build_payload
|
|
@@ -125,7 +125,6 @@ module OmniauthOpenidFederation
|
|
|
125
125
|
metadata: @metadata
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
# Entity Configuration specific claims
|
|
129
128
|
if is_entity_configuration
|
|
130
129
|
payload[:authority_hints] = @authority_hints if @authority_hints
|
|
131
130
|
payload[:trust_marks] = @trust_marks if @trust_marks
|
|
@@ -133,7 +132,6 @@ module OmniauthOpenidFederation
|
|
|
133
132
|
payload[:trust_mark_owners] = @trust_mark_owners if @trust_mark_owners
|
|
134
133
|
end
|
|
135
134
|
|
|
136
|
-
# Subordinate Statement specific claims
|
|
137
135
|
if is_subordinate_statement
|
|
138
136
|
payload[:metadata_policy] = @metadata_policy if @metadata_policy
|
|
139
137
|
payload[:metadata_policy_crit] = @metadata_policy_crit if @metadata_policy_crit
|
|
@@ -141,21 +139,17 @@ module OmniauthOpenidFederation
|
|
|
141
139
|
payload[:source_endpoint] = @source_endpoint if @source_endpoint
|
|
142
140
|
end
|
|
143
141
|
|
|
144
|
-
# Common optional claims
|
|
145
142
|
payload[:crit] = @crit if @crit
|
|
146
143
|
|
|
147
144
|
payload
|
|
148
145
|
end
|
|
149
146
|
|
|
150
147
|
def normalize_jwks(jwks)
|
|
151
|
-
# Ensure JWKS is a hash with "keys" array
|
|
152
148
|
if jwks.is_a?(Hash)
|
|
153
|
-
# If it has :keys or "keys", use as-is
|
|
154
149
|
if jwks.key?(:keys) || jwks.key?("keys")
|
|
155
150
|
keys = jwks[:keys] || jwks["keys"]
|
|
156
151
|
{"keys" => normalize_keys(keys)}
|
|
157
152
|
else
|
|
158
|
-
# If it's just a hash, wrap it
|
|
159
153
|
{"keys" => [jwks]}
|
|
160
154
|
end
|
|
161
155
|
elsif jwks.is_a?(Array)
|
|
@@ -168,7 +162,6 @@ module OmniauthOpenidFederation
|
|
|
168
162
|
def normalize_keys(keys)
|
|
169
163
|
keys.map do |key|
|
|
170
164
|
if key.is_a?(Hash)
|
|
171
|
-
# Convert symbol keys to string keys
|
|
172
165
|
key.transform_keys(&:to_s)
|
|
173
166
|
else
|
|
174
167
|
key
|
|
@@ -17,18 +17,47 @@ module OmniauthOpenidFederation
|
|
|
17
17
|
# @raise [ValidationError] If parsing fails
|
|
18
18
|
def self.parse_for_signed_jwks(entity_statement_path)
|
|
19
19
|
# Determine allowed directories for file path validation
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
# If path is absolute and exists, allow it (for temp files in tests)
|
|
21
|
+
# For absolute paths that don't exist, validate path traversal but allow outside allowed_dirs
|
|
22
|
+
# For relative paths, validate against allowed directories
|
|
23
|
+
path_str = entity_statement_path.to_s
|
|
24
|
+
is_absolute = path_str.start_with?("/", "~")
|
|
25
|
+
|
|
26
|
+
if is_absolute && File.exist?(entity_statement_path)
|
|
27
|
+
validated_path = entity_statement_path
|
|
28
|
+
elsif is_absolute
|
|
29
|
+
# Absolute path - validate path traversal but allow outside allowed_dirs
|
|
30
|
+
begin
|
|
31
|
+
validated_path = Utils.validate_file_path!(
|
|
32
|
+
entity_statement_path,
|
|
33
|
+
allowed_dirs: nil # Allow absolute paths outside config directory
|
|
34
|
+
)
|
|
35
|
+
rescue SecurityError => e
|
|
36
|
+
OmniauthOpenidFederation::Logger.warn("[EntityStatementHelper] Security error: #{e.message}")
|
|
37
|
+
return nil
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
# Relative path - must be in allowed directories
|
|
41
|
+
config = Configuration.config
|
|
42
|
+
allowed_dirs = if defined?(Rails) && Rails.root
|
|
43
|
+
[Rails.root.join("config").to_s]
|
|
44
|
+
elsif config.root_path
|
|
45
|
+
[File.join(config.root_path, "config")]
|
|
46
|
+
end
|
|
26
47
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
48
|
+
begin
|
|
49
|
+
# Validate file path to prevent path traversal
|
|
50
|
+
validated_path = Utils.validate_file_path!(
|
|
51
|
+
entity_statement_path,
|
|
52
|
+
allowed_dirs: allowed_dirs
|
|
53
|
+
)
|
|
54
|
+
rescue SecurityError => e
|
|
55
|
+
# For relative paths with path traversal, raise SecurityError instead of returning nil
|
|
56
|
+
# This is a security violation that should be explicitly handled
|
|
57
|
+
OmniauthOpenidFederation::Logger.warn("[EntityStatementHelper] Security error: #{e.message}")
|
|
58
|
+
raise SecurityError, e.message
|
|
59
|
+
end
|
|
60
|
+
end
|
|
32
61
|
|
|
33
62
|
unless File.exist?(validated_path)
|
|
34
63
|
sanitized_path = Utils.sanitize_path(validated_path)
|