omniauth_openid_federation 1.2.2 → 1.3.0
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 +7 -1
- data/README.md +210 -708
- data/app/controllers/omniauth_openid_federation/federation_controller.rb +13 -0
- data/examples/config/initializers/devise.rb.example +44 -55
- data/examples/config/initializers/federation_endpoint.rb.example +2 -2
- data/examples/config/open_id_connect_config.rb.example +12 -15
- data/examples/config/routes.rb.example +9 -5
- data/lib/omniauth_openid_federation/configuration.rb +8 -0
- data/lib/omniauth_openid_federation/constants.rb +5 -0
- data/lib/omniauth_openid_federation/federation_endpoint.rb +0 -22
- data/lib/omniauth_openid_federation/jwks/decode.rb +0 -15
- data/lib/omniauth_openid_federation/jws.rb +21 -19
- data/lib/omniauth_openid_federation/rack_endpoint.rb +13 -0
- data/lib/omniauth_openid_federation/strategy.rb +143 -194
- data/lib/omniauth_openid_federation/tasks_helper.rb +482 -1
- data/lib/omniauth_openid_federation/validators.rb +316 -6
- data/lib/omniauth_openid_federation/version.rb +1 -1
- data/lib/tasks/omniauth_openid_federation.rake +298 -0
- data/sig/federation.rbs +0 -8
- data/sig/jwks.rbs +0 -6
- data/sig/omniauth_openid_federation.rbs +0 -1
- data/sig/strategy.rbs +0 -2
- metadata +1 -1
|
@@ -50,6 +50,19 @@ module OmniauthOpenidFederation
|
|
|
50
50
|
return
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# Security: Validate entity identifier per OpenID Federation 1.0 spec
|
|
54
|
+
# Entity identifiers must be valid HTTP/HTTPS URIs
|
|
55
|
+
begin
|
|
56
|
+
# Validate and get trimmed value
|
|
57
|
+
subject_entity_id = OmniauthOpenidFederation::Validators.validate_entity_identifier!(subject_entity_id, max_length: 2048)
|
|
58
|
+
rescue SecurityError => e
|
|
59
|
+
render json: {error: "invalid_request", error_description: "Invalid subject entity ID: #{e.message}"}, status: :bad_request
|
|
60
|
+
return
|
|
61
|
+
rescue => e
|
|
62
|
+
render json: {error: "invalid_request", error_description: "Subject entity ID validation failed: #{e.message}"}, status: :bad_request
|
|
63
|
+
return
|
|
64
|
+
end
|
|
65
|
+
|
|
53
66
|
# Validate that subject is not the issuer (invalid request per spec)
|
|
54
67
|
config = OmniauthOpenidFederation::FederationEndpoint.configuration
|
|
55
68
|
if subject_entity_id == config.issuer
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
# Example Devise configuration for OmniAuth OpenID Federation
|
|
2
2
|
# Copy this to config/initializers/devise.rb and customize for your provider
|
|
3
|
+
#
|
|
4
|
+
# This example uses a configuration class pattern (OpenIdConnectConfig)
|
|
5
|
+
# See examples/config/open_id_connect_config.rb.example for the config class
|
|
3
6
|
|
|
4
7
|
require "omniauth_openid_federation"
|
|
5
8
|
|
|
6
9
|
# Configure global settings (optional but recommended)
|
|
7
10
|
OmniauthOpenidFederation.configure do |config|
|
|
11
|
+
if Rails.env.development?
|
|
12
|
+
config.http_options = { ssl: { verify_mode: OpenSSL::SSL::VERIFY_PEER, ca_file: (OpenSSL::X509::DEFAULT_CERT_FILE if File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE)) } }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
config.cache_ttl = 24 * 60 * 60
|
|
16
|
+
config.rotate_on_errors = true
|
|
17
|
+
config.http_timeout = 10
|
|
18
|
+
config.max_retries = 3
|
|
19
|
+
config.verify_ssl = true
|
|
20
|
+
|
|
8
21
|
# Security instrumentation - get notified about security events, MITM attacks, etc.
|
|
9
22
|
# Example with Sentry:
|
|
10
23
|
# config.instrumentation = ->(event, data) do
|
|
@@ -24,51 +37,12 @@ OmniauthOpenidFederation.configure do |config|
|
|
|
24
37
|
# config.instrumentation = ->(event, data) do
|
|
25
38
|
# Rails.logger.warn("[Security] #{event}: #{data.inspect}")
|
|
26
39
|
# end
|
|
27
|
-
|
|
28
|
-
# Cache configuration (optional)
|
|
29
|
-
# config.cache_ttl = 3600 # Refresh provider keys every hour
|
|
30
|
-
# config.rotate_on_errors = true # Auto-handle provider key rotation
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Provider configuration
|
|
34
|
-
provider_issuer = ENV["OPENID_PROVIDER_ISSUER"] || "https://provider.example.com"
|
|
35
|
-
client_id = ENV["OPENID_CLIENT_ID"] || "your-client-id"
|
|
36
|
-
redirect_uri = "#{ENV["APP_URL"] || "https://your-app.com"}/users/auth/openid_federation/callback"
|
|
37
|
-
|
|
38
|
-
# File paths
|
|
39
|
-
private_key_path = Rails.root.join("config", "client-private-key.pem")
|
|
40
|
-
entity_statement_path = Rails.root.join("config", "provider-entity-statement.jwt")
|
|
41
|
-
|
|
42
|
-
# Load private key
|
|
43
|
-
unless File.exist?(private_key_path)
|
|
44
|
-
raise "Private key not found at #{private_key_path}. Generate it first using the example in README."
|
|
45
|
-
end
|
|
46
|
-
private_key = OpenSSL::PKey::RSA.new(File.read(private_key_path))
|
|
47
|
-
|
|
48
|
-
# Resolve endpoints from entity statement or manual configuration
|
|
49
|
-
endpoints = if File.exist?(entity_statement_path)
|
|
50
|
-
# Use entity statement if available (recommended for OpenID Federation)
|
|
51
|
-
OmniauthOpenidFederation::EndpointResolver.resolve(
|
|
52
|
-
entity_statement_path: entity_statement_path.to_s,
|
|
53
|
-
config: {}
|
|
54
|
-
)
|
|
55
|
-
else
|
|
56
|
-
# Fallback to manual configuration
|
|
57
|
-
{
|
|
58
|
-
authorization_endpoint: ENV["OPENID_AUTHORIZATION_ENDPOINT"] || "/oauth2/authorize",
|
|
59
|
-
token_endpoint: ENV["OPENID_TOKEN_ENDPOINT"] || "/oauth2/token",
|
|
60
|
-
userinfo_endpoint: ENV["OPENID_USERINFO_ENDPOINT"] || "/oauth2/userinfo",
|
|
61
|
-
jwks_uri: ENV["OPENID_JWKS_URI"] || "/.well-known/jwks.json",
|
|
62
|
-
audience: provider_issuer
|
|
63
|
-
}
|
|
64
40
|
end
|
|
65
41
|
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
endpoints,
|
|
69
|
-
issuer_uri: URI.parse(provider_issuer)
|
|
70
|
-
)
|
|
42
|
+
# Load configuration from config class
|
|
43
|
+
open_id_config = OpenIdConnectConfig.new
|
|
71
44
|
|
|
45
|
+
if open_id_config.enabled?
|
|
72
46
|
Devise.setup do |config|
|
|
73
47
|
# ... your other Devise configuration ...
|
|
74
48
|
|
|
@@ -107,25 +81,40 @@ Devise.setup do |config|
|
|
|
107
81
|
end
|
|
108
82
|
|
|
109
83
|
config.omniauth :openid_federation,
|
|
84
|
+
strategy_class: OmniAuth::Strategies::OpenIDFederation,
|
|
110
85
|
name: :openid_federation,
|
|
111
86
|
scope: [:openid],
|
|
112
87
|
response_type: "code",
|
|
113
88
|
discovery: true,
|
|
114
|
-
issuer: provider_issuer,
|
|
115
89
|
client_auth_method: :jwt_bearer,
|
|
116
90
|
client_signing_alg: :RS256,
|
|
117
|
-
|
|
118
|
-
|
|
91
|
+
entity_statement_path: open_id_config.entity_statement_absolute_path,
|
|
92
|
+
always_encrypt_request_object: true,
|
|
93
|
+
# Allow-list of custom parameter names to include in signed request object
|
|
94
|
+
# Parameters must be present in request.params and listed here to be included
|
|
95
|
+
request_object_params: [:ftn_spname], # Example: include ftn_spname if present
|
|
96
|
+
# Proc to modify params before adding to signed request object
|
|
97
|
+
# Useful for combining config values with form values, adding config-based params, etc.
|
|
98
|
+
prepare_request_object_params: proc do |params|
|
|
99
|
+
# Example: Combine config acr_values with form acr_values
|
|
100
|
+
form_acr_values = params["acr_values"]&.to_s&.strip
|
|
101
|
+
config_acr_values = open_id_config.acr_values.to_s.strip
|
|
102
|
+
|
|
103
|
+
if config_acr_values.present? && form_acr_values.present?
|
|
104
|
+
params["acr_values"] = "#{config_acr_values} #{form_acr_values}".strip
|
|
105
|
+
elsif config_acr_values.present?
|
|
106
|
+
params["acr_values"] = config_acr_values
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Example: Add custom parameter from config
|
|
110
|
+
params["ftn_spname"] = open_id_config.ftn_spname if open_id_config.ftn_spname.present?
|
|
111
|
+
|
|
112
|
+
params
|
|
113
|
+
end,
|
|
119
114
|
client_options: {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
scheme: URI.parse(provider_issuer).scheme,
|
|
124
|
-
host: URI.parse(provider_issuer).host,
|
|
125
|
-
authorization_endpoint: endpoints[:authorization_endpoint],
|
|
126
|
-
token_endpoint: endpoints[:token_endpoint],
|
|
127
|
-
userinfo_endpoint: endpoints[:userinfo_endpoint],
|
|
128
|
-
jwks_uri: endpoints[:jwks_uri]
|
|
115
|
+
identifier: open_id_config.client_id,
|
|
116
|
+
redirect_uri: open_id_config.redirect_uri,
|
|
117
|
+
private_key: open_id_config.private_key
|
|
129
118
|
}
|
|
130
119
|
end
|
|
131
|
-
|
|
120
|
+
end
|
|
@@ -187,7 +187,7 @@ end
|
|
|
187
187
|
# ============================================================================
|
|
188
188
|
# Add to config/routes.rb:
|
|
189
189
|
#
|
|
190
|
-
# OmniauthOpenidFederation::
|
|
190
|
+
# mount OmniauthOpenidFederation::Engine => "/"
|
|
191
191
|
#
|
|
192
192
|
# This mounts all endpoints:
|
|
193
193
|
# - GET /.well-known/openid-federation (entity statement)
|
|
@@ -202,5 +202,5 @@ end
|
|
|
202
202
|
# get "/.well-known/signed-jwks.json", to: "omniauth_openid_federation/federation#signed_jwks"
|
|
203
203
|
|
|
204
204
|
Rails.logger.info "[FederationEndpoint] Configured. Add the route in config/routes.rb:"
|
|
205
|
-
Rails.logger.info " OmniauthOpenidFederation::
|
|
205
|
+
Rails.logger.info " mount OmniauthOpenidFederation::Engine => \"/\""
|
|
206
206
|
|
|
@@ -62,8 +62,6 @@ class OpenIdConnectConfig < ApplicationConfig
|
|
|
62
62
|
:client_entity_statement_path,
|
|
63
63
|
:client_entity_statement_url,
|
|
64
64
|
:client_entity_identifier,
|
|
65
|
-
:ftn_spname,
|
|
66
|
-
:acr_values,
|
|
67
65
|
:organization_name
|
|
68
66
|
|
|
69
67
|
# Load private key from file or base64 encoded string
|
|
@@ -128,8 +126,8 @@ class OpenIdConnectConfig < ApplicationConfig
|
|
|
128
126
|
# Entity statements MUST be available at /.well-known/openid-federation
|
|
129
127
|
# URL is the source of truth per OpenID Federation spec
|
|
130
128
|
def entity_statement_absolute_path
|
|
131
|
-
return
|
|
132
|
-
@entity_statement_absolute_path ||= Rails.root.join(entity_statement_path)
|
|
129
|
+
return Rails.root.join("config", ".federation-entity-statement.jwt").to_s if entity_statement_path.blank?
|
|
130
|
+
@entity_statement_absolute_path ||= Rails.root.join(entity_statement_path).to_s
|
|
133
131
|
end
|
|
134
132
|
|
|
135
133
|
# Get provider entity statement URL
|
|
@@ -143,7 +141,7 @@ class OpenIdConnectConfig < ApplicationConfig
|
|
|
143
141
|
# Get client entity statement absolute path
|
|
144
142
|
def client_entity_statement_absolute_path
|
|
145
143
|
return nil if client_entity_statement_path.blank?
|
|
146
|
-
@client_entity_statement_absolute_path ||= Rails.root.join(client_entity_statement_path)
|
|
144
|
+
@client_entity_statement_absolute_path ||= Rails.root.join(client_entity_statement_path).to_s
|
|
147
145
|
end
|
|
148
146
|
|
|
149
147
|
# Get client entity statement path for strategy configuration
|
|
@@ -165,7 +163,6 @@ class OpenIdConnectConfig < ApplicationConfig
|
|
|
165
163
|
# Uses standard path /.well-known/openid-federation (mounted by library)
|
|
166
164
|
def client_entity_statement_url
|
|
167
165
|
return values[:client_entity_statement_url] if values[:client_entity_statement_url].present?
|
|
168
|
-
# Default to standard federation endpoint if not explicitly configured
|
|
169
166
|
app_url = ENV["APP_URL"] || "https://your-app.example.com"
|
|
170
167
|
"#{app_url}/.well-known/openid-federation"
|
|
171
168
|
end
|
|
@@ -174,16 +171,15 @@ end
|
|
|
174
171
|
# Usage in initializer (config/initializers/omniauth_openid_federation.rb):
|
|
175
172
|
#
|
|
176
173
|
# open_id_config = OpenIdConnectConfig.new
|
|
177
|
-
# if open_id_config.enabled?
|
|
174
|
+
# if open_id_config.enabled?
|
|
178
175
|
# app_url = ENV["APP_URL"] || "https://your-app.example.com"
|
|
179
176
|
#
|
|
180
177
|
# OmniauthOpenidFederation::FederationEndpoint.auto_configure(
|
|
181
178
|
# issuer: app_url,
|
|
182
|
-
# #
|
|
183
|
-
# signing_key: open_id_config.signing_key,
|
|
184
|
-
# encryption_key: open_id_config.encryption_key,
|
|
185
|
-
# #
|
|
186
|
-
# # private_key: open_id_config.private_key,
|
|
179
|
+
# # RECOMMENDED: Use separate signing_key and encryption_key for production
|
|
180
|
+
# # signing_key: open_id_config.signing_key,
|
|
181
|
+
# # encryption_key: open_id_config.encryption_key,
|
|
182
|
+
# private_key: open_id_config.private_key, # DEV/TESTING ONLY - not recommended for production
|
|
187
183
|
# entity_statement_path: open_id_config.client_entity_statement_absolute_path,
|
|
188
184
|
# metadata: {
|
|
189
185
|
# openid_relying_party: {
|
|
@@ -202,9 +198,10 @@ end
|
|
|
202
198
|
# organization_name: open_id_config.organization_name
|
|
203
199
|
# }
|
|
204
200
|
# },
|
|
205
|
-
# expiration_seconds:
|
|
206
|
-
# jwks_cache_ttl:
|
|
207
|
-
# auto_provision_keys: true
|
|
201
|
+
# expiration_seconds: 86400,
|
|
202
|
+
# jwks_cache_ttl: 3600,
|
|
203
|
+
# auto_provision_keys: true,
|
|
204
|
+
# key_rotation_period: 90.days.to_i
|
|
208
205
|
# )
|
|
209
206
|
# end
|
|
210
207
|
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# Example routes.rb showing how to mount the federation endpoint
|
|
2
2
|
Rails.application.routes.draw do
|
|
3
|
-
# Mount
|
|
3
|
+
# Mount OpenID Federation engine early to ensure well-known routes are registered before catch-all routes
|
|
4
4
|
# This enables the /.well-known/openid-federation endpoint
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# get "/.well-known/openid-federation", to: "omniauth_openid_federation/federation#show", as: :openid_federation
|
|
5
|
+
if OpenIdConnectConfig.enabled?
|
|
6
|
+
mount OmniauthOpenidFederation::Engine => "/"
|
|
7
|
+
end
|
|
9
8
|
|
|
10
9
|
# Your other routes...
|
|
10
|
+
devise_for :users, controllers: {
|
|
11
|
+
omniauth_callbacks: "users/omniauth_callbacks"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# ... rest of your routes ...
|
|
11
15
|
end
|
|
12
16
|
|
|
@@ -84,6 +84,13 @@ module OmniauthOpenidFederation
|
|
|
84
84
|
# config.instrumentation = nil
|
|
85
85
|
attr_accessor :instrumentation
|
|
86
86
|
|
|
87
|
+
# Maximum string length for request parameters (default: 8192 / 8KB)
|
|
88
|
+
# Prevents DoS attacks while allowing legitimate use cases (e.g., encrypted JWT authorization codes)
|
|
89
|
+
# @return [Integer] Maximum string length in characters
|
|
90
|
+
# @example
|
|
91
|
+
# config.max_string_length = 16384 # Increase to 16KB
|
|
92
|
+
attr_accessor :max_string_length
|
|
93
|
+
|
|
87
94
|
def initialize
|
|
88
95
|
@verify_ssl = true # Default to secure
|
|
89
96
|
@cache_ttl = nil # Default: manual rotation (never expires)
|
|
@@ -96,6 +103,7 @@ module OmniauthOpenidFederation
|
|
|
96
103
|
@root_path = nil
|
|
97
104
|
@clock_skew_tolerance = 60 # Default: 60 seconds clock skew tolerance
|
|
98
105
|
@instrumentation = nil # Default: no instrumentation
|
|
106
|
+
@max_string_length = 8192 # Default: 8KB - prevents DoS while allowing legitimate use cases
|
|
99
107
|
end
|
|
100
108
|
|
|
101
109
|
# Configure the gem
|
|
@@ -9,5 +9,10 @@ module OmniauthOpenidFederation
|
|
|
9
9
|
|
|
10
10
|
# Maximum retry delay in seconds (prevents unbounded retry delays)
|
|
11
11
|
MAX_RETRY_DELAY_SECONDS = 60
|
|
12
|
+
|
|
13
|
+
# Maximum string length for request parameters (8KB)
|
|
14
|
+
# Prevents DoS attacks while allowing legitimate use cases (e.g., encrypted JWT authorization codes)
|
|
15
|
+
# Use Configuration.config.max_string_length for runtime configuration instead of patching this constant
|
|
16
|
+
MAX_STRING_LENGTH = 8192
|
|
12
17
|
end
|
|
13
18
|
end
|
|
@@ -64,8 +64,6 @@ module OmniauthOpenidFederation
|
|
|
64
64
|
# # In config/routes.rb (Rails)
|
|
65
65
|
# get "/.well-known/openid-federation", to: "omniauth_openid_federation/federation#show"
|
|
66
66
|
#
|
|
67
|
-
# # Or use the provided route helper
|
|
68
|
-
# OmniauthOpenidFederation::FederationEndpoint.mount_routes
|
|
69
67
|
class FederationEndpoint
|
|
70
68
|
class << self
|
|
71
69
|
# Configure the federation endpoint
|
|
@@ -558,26 +556,6 @@ module OmniauthOpenidFederation
|
|
|
558
556
|
# - GET /.well-known/jwks.json (standard JWKS)
|
|
559
557
|
# - GET /.well-known/signed-jwks.json (signed JWKS)
|
|
560
558
|
#
|
|
561
|
-
# ALTERNATIVE: Use mount_routes helper (for backward compatibility or custom paths):
|
|
562
|
-
# Rails.application.routes.draw do
|
|
563
|
-
# OmniauthOpenidFederation::FederationEndpoint.mount_routes(self)
|
|
564
|
-
# end
|
|
565
|
-
#
|
|
566
|
-
# @param router [ActionDispatch::Routing::Mapper] The routes mapper (pass `self` from routes.rb)
|
|
567
|
-
# @param entity_statement_path [String] Path for entity statement endpoint (default: "/.well-known/openid-federation")
|
|
568
|
-
# @param fetch_path [String] Path for fetch endpoint (default: "/.well-known/openid-federation/fetch")
|
|
569
|
-
# @param jwks_path [String] Path for standard JWKS endpoint (default: "/.well-known/jwks.json")
|
|
570
|
-
# @param signed_jwks_path [String] Path for signed JWKS endpoint (default: "/.well-known/signed-jwks.json")
|
|
571
|
-
# @param as [String, Symbol] Route name prefix (default: :openid_federation)
|
|
572
|
-
# @deprecated Use `mount OmniauthOpenidFederation::Engine => "/"` instead (Rails-idiomatic way)
|
|
573
|
-
def mount_routes(router, entity_statement_path: "/.well-known/openid-federation", fetch_path: "/.well-known/openid-federation/fetch", jwks_path: "/.well-known/jwks.json", signed_jwks_path: "/.well-known/signed-jwks.json", as: :openid_federation)
|
|
574
|
-
# Controller uses Rails-conventional naming (OmniauthOpenidFederation)
|
|
575
|
-
# which matches natural inflection from omniauth_openid_federation
|
|
576
|
-
router.get entity_statement_path, to: "omniauth_openid_federation/federation#show", as: as
|
|
577
|
-
router.get fetch_path, to: "omniauth_openid_federation/federation#fetch", as: :"#{as}_fetch"
|
|
578
|
-
router.get jwks_path, to: "omniauth_openid_federation/federation#jwks", as: :"#{as}_jwks"
|
|
579
|
-
router.get signed_jwks_path, to: "omniauth_openid_federation/federation#signed_jwks", as: :"#{as}_signed_jwks"
|
|
580
|
-
end
|
|
581
559
|
|
|
582
560
|
# Generate fresh signing and encryption keys and write entity statement to file
|
|
583
561
|
#
|
|
@@ -155,21 +155,6 @@ module OmniauthOpenidFederation
|
|
|
155
155
|
)
|
|
156
156
|
end
|
|
157
157
|
end
|
|
158
|
-
|
|
159
|
-
# Decode JWT using jwt gem (legacy method name kept for backward compatibility)
|
|
160
|
-
#
|
|
161
|
-
# @param encoded_jwt [String] The JWT to decode
|
|
162
|
-
# @param jwks_uri [String] The JWKS URI for key lookup
|
|
163
|
-
# @param retried [Boolean] Internal flag for retry logic (default: false)
|
|
164
|
-
# @param entity_statement_keys [Hash, Array, nil] Entity statement keys for validation
|
|
165
|
-
# @return [Array<Hash>] Array with [payload, header]
|
|
166
|
-
# @raise [ValidationError] If JWT validation fails
|
|
167
|
-
# @raise [SignatureError] If signature verification fails
|
|
168
|
-
# @deprecated Use jwt() method instead. This method will be removed in a future version.
|
|
169
|
-
def self.json_jwt(encoded_jwt, jwks_uri, retried: false, entity_statement_keys: nil)
|
|
170
|
-
OmniauthOpenidFederation::Logger.warn("[Jwks::Decode] json_jwt is deprecated. Use jwt() method instead.")
|
|
171
|
-
jwt(encoded_jwt, jwks_uri, retried: retried, entity_statement_keys: entity_statement_keys)
|
|
172
|
-
end
|
|
173
158
|
end
|
|
174
159
|
end
|
|
175
160
|
end
|
|
@@ -63,10 +63,6 @@ module OmniauthOpenidFederation
|
|
|
63
63
|
STATE_BYTES = 16 # Number of hex bytes for state parameter
|
|
64
64
|
|
|
65
65
|
attr_accessor :private_key, :state, :nonce
|
|
66
|
-
# Provider-specific extension parameters (outside JWT)
|
|
67
|
-
# Some providers may require additional parameters that are not part of the JWT
|
|
68
|
-
# @deprecated Use request_object_params option in strategy instead (adds params to the JWT request object)
|
|
69
|
-
attr_accessor :ftn_spname
|
|
70
66
|
|
|
71
67
|
# Initialize JWT request object builder
|
|
72
68
|
#
|
|
@@ -114,21 +110,27 @@ module OmniauthOpenidFederation
|
|
|
114
110
|
key_source: :local,
|
|
115
111
|
client_entity_statement: nil
|
|
116
112
|
)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
@
|
|
121
|
-
@
|
|
122
|
-
@
|
|
123
|
-
@
|
|
124
|
-
@
|
|
125
|
-
@
|
|
126
|
-
@
|
|
127
|
-
@
|
|
128
|
-
@
|
|
129
|
-
|
|
130
|
-
@
|
|
131
|
-
@
|
|
113
|
+
# Security: User input parameters are validated and sanitized before reaching here
|
|
114
|
+
# Configuration parameters are trusted and only trimmed for consistency
|
|
115
|
+
# Store trimmed values to ensure consistency
|
|
116
|
+
@client_id = client_id.to_s.strip
|
|
117
|
+
@redirect_uri = redirect_uri.to_s.strip
|
|
118
|
+
@scope = scope.to_s.strip
|
|
119
|
+
@issuer = issuer&.to_s&.strip
|
|
120
|
+
@audience = audience&.to_s&.strip
|
|
121
|
+
@state = state&.to_s&.strip || SecureRandom.hex(STATE_BYTES)
|
|
122
|
+
@nonce = nonce&.to_s&.strip
|
|
123
|
+
@response_type = response_type.to_s.strip
|
|
124
|
+
@response_mode = response_mode&.to_s&.strip
|
|
125
|
+
# User input parameters (already sanitized)
|
|
126
|
+
@login_hint = login_hint&.to_s&.strip
|
|
127
|
+
@ui_locales = ui_locales&.to_s&.strip
|
|
128
|
+
@claims_locales = claims_locales&.to_s&.strip
|
|
129
|
+
# Configuration parameters (trusted, only trimmed)
|
|
130
|
+
@prompt = prompt&.to_s&.strip
|
|
131
|
+
@hd = hd&.to_s&.strip
|
|
132
|
+
# User input parameter (already sanitized)
|
|
133
|
+
@acr_values = acr_values&.to_s&.strip
|
|
132
134
|
@extra_params = extra_params
|
|
133
135
|
@jwks = jwks
|
|
134
136
|
@entity_statement_path = entity_statement_path
|
|
@@ -25,9 +25,11 @@
|
|
|
25
25
|
require "rack"
|
|
26
26
|
require "json"
|
|
27
27
|
require "digest"
|
|
28
|
+
require "uri"
|
|
28
29
|
require_relative "cache_adapter"
|
|
29
30
|
require_relative "federation_endpoint"
|
|
30
31
|
require_relative "logger"
|
|
32
|
+
require_relative "constants"
|
|
31
33
|
|
|
32
34
|
module OmniauthOpenidFederation
|
|
33
35
|
class RackEndpoint
|
|
@@ -117,6 +119,17 @@ module OmniauthOpenidFederation
|
|
|
117
119
|
return error_response(400, {error: "invalid_request", error_description: "Missing required parameter: sub"}.to_json)
|
|
118
120
|
end
|
|
119
121
|
|
|
122
|
+
# Security: Validate entity identifier per OpenID Federation 1.0 spec
|
|
123
|
+
# Entity identifiers must be valid HTTP/HTTPS URIs
|
|
124
|
+
begin
|
|
125
|
+
# Validate and get trimmed value
|
|
126
|
+
subject_entity_id = OmniauthOpenidFederation::Validators.validate_entity_identifier!(subject_entity_id)
|
|
127
|
+
rescue SecurityError => e
|
|
128
|
+
return error_response(400, {error: "invalid_request", error_description: "Invalid subject entity ID: #{e.message}"}.to_json)
|
|
129
|
+
rescue => e
|
|
130
|
+
return error_response(400, {error: "invalid_request", error_description: "Subject entity ID validation failed: #{e.message}"}.to_json)
|
|
131
|
+
end
|
|
132
|
+
|
|
120
133
|
# Validate that subject is not the issuer (invalid request per spec)
|
|
121
134
|
config = OmniauthOpenidFederation::FederationEndpoint.configuration
|
|
122
135
|
if subject_entity_id == config.issuer
|