omniauth_openid_federation 1.0.0 → 1.2.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 +20 -0
- data/README.md +109 -9
- data/SECURITY.md +10 -111
- data/examples/app/controllers/users/omniauth_callbacks_controller.rb.example +4 -0
- data/examples/config/initializers/devise.rb.example +34 -0
- data/lib/omniauth_openid_federation/engine.rb +21 -0
- data/lib/omniauth_openid_federation/entity_statement_reader.rb +7 -0
- data/lib/omniauth_openid_federation/federation/entity_statement_validator.rb +1 -1
- data/lib/omniauth_openid_federation/federation_endpoint.rb +8 -8
- data/lib/omniauth_openid_federation/instrumentation.rb +16 -0
- data/lib/omniauth_openid_federation/jwks/decode.rb +2 -1
- data/lib/omniauth_openid_federation/jws.rb +1 -7
- data/lib/omniauth_openid_federation/railtie.rb +3 -17
- data/lib/omniauth_openid_federation/strategy.rb +89 -4
- data/lib/omniauth_openid_federation/utils.rb +2 -0
- data/lib/omniauth_openid_federation/version.rb +1 -1
- data/lib/omniauth_openid_federation.rb +2 -1
- metadata +12 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e299269e65c33735c84f2bec3ee0164bc5cb136c8dac5088be188998d826d5bc
|
|
4
|
+
data.tar.gz: c35464efdf1af7957456641275cfa3a374a1600a0570fa7db7a6ceeb1a31417f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9e96511bd8c6972d774435015822297a44892e263b13187ef1a52741186288bf25ded9b15bb0f3c5d856796e7e3b30df9d845c5c3f67987bf7fcd94160c26eda
|
|
7
|
+
data.tar.gz: 6c5a94f3bb8f429cc9dd583b98032418858520cc93658e9fdba1fe7192d8cb03b9b7e11575d22f44ce5e18cd2cf7bf05f5d512d7a7f96a47404dde00445923e9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 1.2.0 (2025-11-27)
|
|
4
|
+
|
|
5
|
+
- Created `OmniauthOpenidFederation::Engine` class inheriting from `Rails::Engine`
|
|
6
|
+
- Engine provides controllers via standard Rails autoloading mechanisms
|
|
7
|
+
- Routes are now defined in Engine's `config/routes.rb` file
|
|
8
|
+
- Routes must now be mounted using `mount OmniauthOpenidFederation::Engine => "/"` in `config/routes.rb`
|
|
9
|
+
- `FederationEndpoint.mount_routes` is still available for backward compatibility
|
|
10
|
+
|
|
11
|
+
## 1.1.0 (2025-11-26)
|
|
12
|
+
|
|
13
|
+
- Enhanced instrumentation: All blocking exceptions automatically reported through instrumentation system, including OmniAuth middleware errors (like AuthenticityTokenProtection)
|
|
14
|
+
- CSRF protection instrumentation: New authenticity_error event type for reporting OmniAuth CSRF protection failures
|
|
15
|
+
- Comprehensive error reporting: Override fail! method in strategy to catch and instrument all authentication failures
|
|
16
|
+
- CSRF protection documentation: Added comprehensive Step 7 in README explaining CSRF protection configuration for both request and callback phases
|
|
17
|
+
- CSRF configuration examples: Added complete examples in examples/config/initializers/devise.rb.example and examples/app/controllers/users/omniauth_callbacks_controller.rb.example
|
|
18
|
+
- Deprecation warnings: Added runtime deprecation warnings for json_jwt method and ftn_spname option to guide users to recommended alternatives
|
|
19
|
+
- Code cleanup: Removed deprecated load_signing_key method (unused, returned nil)
|
|
20
|
+
- Updated deprecation notices: Fixed deprecation notices to reference correct replacement methods (request_object_params instead of non-existent provider_extension_params)
|
|
21
|
+
- Renamed option: `allow_authorize_params` → `request_object_params` for clarity (uses RFC 9101 terminology, clearly indicates params go into JWT request object)
|
|
22
|
+
|
|
3
23
|
## 1.0.0 (2025-11-26)
|
|
4
24
|
|
|
5
25
|
- Initial public release, production-ready
|
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)
|
|
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)
|
|
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
|
|
|
@@ -128,6 +128,8 @@ config.omniauth :openid_federation,
|
|
|
128
128
|
- `entity_statement_path` is optional - only for offline development (cached copy)
|
|
129
129
|
- `discovery: true` automatically discovers all endpoints from entity statement
|
|
130
130
|
|
|
131
|
+
**Important**: Don't forget to configure CSRF protection (see [Step 7: Configure CSRF Protection](#step-7-configure-csrf-protection)) to ensure proper security for both request and callback phases.
|
|
132
|
+
|
|
131
133
|
#### For OmniAuth (non-Rails)
|
|
132
134
|
|
|
133
135
|
```ruby
|
|
@@ -176,7 +178,11 @@ OmniauthOpenidFederation::FederationEndpoint.auto_configure(
|
|
|
176
178
|
|
|
177
179
|
```ruby
|
|
178
180
|
# config/routes.rb
|
|
179
|
-
|
|
181
|
+
# RECOMMENDED: Mount the Engine (Rails-idiomatic way)
|
|
182
|
+
mount OmniauthOpenidFederation::Engine => "/"
|
|
183
|
+
|
|
184
|
+
# ALTERNATIVE: Use mount_routes helper (for backward compatibility)
|
|
185
|
+
# OmniauthOpenidFederation::FederationEndpoint.mount_routes(self)
|
|
180
186
|
```
|
|
181
187
|
|
|
182
188
|
**Key Points**:
|
|
@@ -186,34 +192,109 @@ OmniauthOpenidFederation::FederationEndpoint.mount_routes(self)
|
|
|
186
192
|
|
|
187
193
|
### Step 6: Add Routes
|
|
188
194
|
|
|
189
|
-
####
|
|
195
|
+
#### Mount the Engine (Required for Federation Endpoints)
|
|
196
|
+
|
|
197
|
+
The gem provides a Rails Engine that serves the well-known OpenID Federation endpoints. Mount it in your routes:
|
|
190
198
|
|
|
191
199
|
```ruby
|
|
192
200
|
# config/routes.rb
|
|
193
201
|
Rails.application.routes.draw do
|
|
202
|
+
# Mount the Engine to enable /.well-known/openid-federation endpoint
|
|
203
|
+
mount OmniauthOpenidFederation::Engine => "/"
|
|
204
|
+
|
|
205
|
+
# Your other routes...
|
|
194
206
|
devise_for :users, controllers: {
|
|
195
207
|
omniauth_callbacks: "users/omniauth_callbacks"
|
|
196
208
|
}
|
|
197
209
|
end
|
|
198
210
|
```
|
|
199
211
|
|
|
200
|
-
|
|
212
|
+
**Note**: The Engine is mounted at root (`"/"`) because OpenID Federation requires endpoints at specific well-known paths (e.g., `/.well-known/openid-federation`). The Engine's routes are defined in the gem and automatically available when mounted.
|
|
213
|
+
|
|
214
|
+
#### For OmniAuth (Non-Devise)
|
|
201
215
|
|
|
202
216
|
```ruby
|
|
203
217
|
# config/routes.rb
|
|
204
218
|
Rails.application.routes.draw do
|
|
219
|
+
mount OmniauthOpenidFederation::Engine => "/"
|
|
220
|
+
|
|
205
221
|
get "/auth/:provider/callback", to: "sessions#create"
|
|
206
222
|
get "/auth/failure", to: "sessions#failure"
|
|
207
223
|
end
|
|
208
224
|
```
|
|
209
225
|
|
|
210
|
-
|
|
226
|
+
#### Alternative: Manual Route Mounting (Backward Compatibility)
|
|
227
|
+
|
|
228
|
+
If you need custom paths or prefer manual route definition, you can use the `mount_routes` helper (deprecated):
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
# config/routes.rb
|
|
232
|
+
Rails.application.routes.draw do
|
|
233
|
+
# Use mount_routes helper for custom paths (deprecated - prefer Engine mounting)
|
|
234
|
+
OmniauthOpenidFederation::FederationEndpoint.mount_routes(self)
|
|
235
|
+
# ... your other routes
|
|
236
|
+
end
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Step 7: Configure CSRF Protection
|
|
240
|
+
|
|
241
|
+
OmniAuth requires CSRF protection configuration to handle both the request phase (initiating OAuth) and callback phase (external provider redirect).
|
|
242
|
+
|
|
243
|
+
**Important**: The request phase uses Rails CSRF tokens (forms must include them), while the callback phase uses OAuth state parameter for CSRF protection (external providers cannot include Rails CSRF tokens).
|
|
244
|
+
|
|
245
|
+
#### For Devise (Rails)
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
# config/initializers/devise.rb
|
|
249
|
+
if defined?(OmniAuth)
|
|
250
|
+
OmniAuth.config.allowed_request_methods = [:post]
|
|
251
|
+
OmniAuth.config.silence_get_warning = false
|
|
252
|
+
|
|
253
|
+
# Configure CSRF validation to check tokens only for request phase (initiating OAuth)
|
|
254
|
+
# Callback phase uses OAuth state parameter for CSRF protection (validated in strategy)
|
|
255
|
+
# This ensures:
|
|
256
|
+
# - Request phase: Forms must include Rails CSRF tokens (standard Rails protection)
|
|
257
|
+
# - Callback phase: OAuth state parameter provides CSRF protection (external providers can't include Rails tokens)
|
|
258
|
+
OmniAuth.config.request_validation_phase = lambda do |env|
|
|
259
|
+
request = Rack::Request.new(env)
|
|
260
|
+
path = request.path
|
|
261
|
+
|
|
262
|
+
# Skip CSRF validation for callback paths (external providers can't include Rails CSRF tokens)
|
|
263
|
+
# OAuth state parameter provides CSRF protection for callbacks (validated in OpenIDFederation strategy)
|
|
264
|
+
return true if path.end_with?("/callback")
|
|
265
|
+
|
|
266
|
+
# For request phase, use Rails' standard CSRF token validation
|
|
267
|
+
# This ensures forms must include valid CSRF tokens when initiating OAuth
|
|
268
|
+
session = env["rack.session"] || {}
|
|
269
|
+
token = request.params["authenticity_token"] || request.get_header("X-CSRF-Token")
|
|
270
|
+
expected_token = session[:_csrf_token] || session["_csrf_token"]
|
|
271
|
+
|
|
272
|
+
# Validate CSRF token using constant-time comparison
|
|
273
|
+
if token.present? && expected_token.present?
|
|
274
|
+
ActiveSupport::SecurityUtils.secure_compare(token.to_s, expected_token.to_s)
|
|
275
|
+
else
|
|
276
|
+
false
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Security Notes**:
|
|
283
|
+
- **Request phase** (initiating OAuth): Forms must include Rails CSRF tokens via `button_to` or `form_with` helpers
|
|
284
|
+
- **Callback phase** (external provider redirect): OAuth `state` parameter provides CSRF protection (automatically validated in `OpenIDFederation` strategy using constant-time comparison)
|
|
285
|
+
- Both layers provide equivalent security - Rails CSRF tokens for request phase, OAuth state parameter for callbacks
|
|
286
|
+
|
|
287
|
+
### Step 8: Create Callback Controller
|
|
211
288
|
|
|
212
289
|
#### For Devise
|
|
213
290
|
|
|
214
291
|
```ruby
|
|
215
292
|
# app/controllers/users/omniauth_callbacks_controller.rb
|
|
216
293
|
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|
294
|
+
# Skip Rails CSRF protection for OAuth callbacks
|
|
295
|
+
# OAuth callbacks from external providers cannot include Rails CSRF tokens
|
|
296
|
+
# CSRF protection is handled by OAuth state parameter validation in the strategy
|
|
297
|
+
skip_before_action :verify_authenticity_token, only: [:openid_federation, :failure]
|
|
217
298
|
skip_before_action :authenticate_user!, only: [:openid_federation, :failure]
|
|
218
299
|
|
|
219
300
|
def openid_federation
|
|
@@ -233,7 +314,9 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|
|
233
314
|
end
|
|
234
315
|
```
|
|
235
316
|
|
|
236
|
-
|
|
317
|
+
**Note**: The `skip_before_action :verify_authenticity_token` is required because Rails' `protect_from_forgery` in `ApplicationController` checks CSRF tokens for all POST requests. External providers cannot include Rails CSRF tokens in callbacks, so we skip Rails' check while relying on OAuth state parameter validation (handled by the strategy).
|
|
318
|
+
|
|
319
|
+
### Step 9: Create User Model Method
|
|
237
320
|
|
|
238
321
|
```ruby
|
|
239
322
|
# app/models/user.rb
|
|
@@ -363,7 +446,8 @@ end
|
|
|
363
446
|
```
|
|
364
447
|
|
|
365
448
|
**Instrumented Events**:
|
|
366
|
-
- `csrf_detected` - CSRF attack detected (state mismatch)
|
|
449
|
+
- `csrf_detected` - CSRF attack detected (state mismatch in callback phase)
|
|
450
|
+
- `authenticity_error` - OmniAuth CSRF protection blocked request (Rails CSRF token validation failed in request phase)
|
|
367
451
|
- `signature_verification_failed` - JWT signature verification failed (possible MITM)
|
|
368
452
|
- `decryption_failed` - Token decryption failed (possible MITM or key mismatch)
|
|
369
453
|
- `token_validation_failed` - Token validation failed (possible tampering)
|
|
@@ -372,9 +456,14 @@ end
|
|
|
372
456
|
- `entity_statement_validation_failed` - Entity statement validation failed (possible MITM)
|
|
373
457
|
- `fingerprint_mismatch` - Entity statement fingerprint mismatch (possible MITM)
|
|
374
458
|
- `trust_chain_validation_failed` - Trust chain validation failed
|
|
375
|
-
- `unexpected_authentication_break` - Unexpected authentication failure
|
|
459
|
+
- `unexpected_authentication_break` - Unexpected authentication failure (missing code, token exchange errors, unknown errors)
|
|
376
460
|
- `missing_required_claims` - Token missing required claims
|
|
377
461
|
|
|
462
|
+
**Note**: All blocking exceptions are automatically reported through instrumentation, including:
|
|
463
|
+
- OmniAuth middleware errors (like `AuthenticityTokenProtection` blocking requests)
|
|
464
|
+
- Strategy-level errors (CSRF detected, missing code, token exchange failures)
|
|
465
|
+
- Unknown error types (reported as `unexpected_authentication_break`)
|
|
466
|
+
|
|
378
467
|
**Security Note**: All sensitive data (tokens, keys, fingerprints) is automatically sanitized before being sent to your instrumentation callback.
|
|
379
468
|
|
|
380
469
|
**Key Rotation Types**:
|
|
@@ -521,7 +610,11 @@ OmniauthOpenidFederation::FederationEndpoint.auto_configure(
|
|
|
521
610
|
|
|
522
611
|
```ruby
|
|
523
612
|
# config/routes.rb
|
|
524
|
-
|
|
613
|
+
# RECOMMENDED: Mount the Engine (Rails-idiomatic way)
|
|
614
|
+
mount OmniauthOpenidFederation::Engine => "/"
|
|
615
|
+
|
|
616
|
+
# ALTERNATIVE: Use mount_routes helper (for backward compatibility)
|
|
617
|
+
# OmniauthOpenidFederation::FederationEndpoint.mount_routes(self)
|
|
525
618
|
```
|
|
526
619
|
|
|
527
620
|
**What `auto_configure` does automatically**:
|
|
@@ -742,6 +835,13 @@ See inline code documentation for complete API reference.
|
|
|
742
835
|
- Provider may have rotated keys (auto-handled with `rotate_on_errors: true`)
|
|
743
836
|
- Clear cache: `Rails.cache.delete_matched("openid_federation_jwks_*")`
|
|
744
837
|
|
|
838
|
+
**"Attack prevented by OmniAuth::AuthenticityTokenProtection" or "OmniAuth::AuthenticityError"**
|
|
839
|
+
- **Request phase (initiating OAuth)**: Ensure forms include Rails CSRF tokens using `button_to` or `form_with` helpers
|
|
840
|
+
- **Callback phase (external provider redirect)**: Ensure CSRF protection is configured correctly (see [Step 7: Configure CSRF Protection](#step-7-configure-csrf-protection))
|
|
841
|
+
- Verify `OmniAuth.config.request_validation_phase` is configured to skip CSRF validation for callback paths
|
|
842
|
+
- Ensure `skip_before_action :verify_authenticity_token` is present in the callback controller for callback actions
|
|
843
|
+
- Check that OAuth state parameter validation is working (handled automatically by the strategy)
|
|
844
|
+
|
|
745
845
|
## Security
|
|
746
846
|
|
|
747
847
|
See [SECURITY.md](SECURITY.md) for detailed security features, protections, and vulnerability reporting.
|
data/SECURITY.md
CHANGED
|
@@ -4,126 +4,25 @@
|
|
|
4
4
|
|
|
5
5
|
**Do NOT** open a public GitHub issue for security vulnerabilities.
|
|
6
6
|
|
|
7
|
-
Email security details to: **
|
|
7
|
+
Email security details to: **security@kiskolabs.com**
|
|
8
8
|
|
|
9
9
|
Include: description, steps to reproduce, potential impact, and suggested fix (if available).
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- Acknowledgment within 48 hours
|
|
13
|
-
- Initial assessment within 7 days
|
|
14
|
-
- Coordinated disclosure after patching
|
|
11
|
+
### Response Timeline
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
- We will acknowledge receipt of your report
|
|
14
|
+
- We will provide an initial assessment
|
|
15
|
+
- We will keep you informed of our progress and resolution timeline
|
|
17
16
|
|
|
18
|
-
###
|
|
17
|
+
### Disclosure Policy
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
**✅ Path Traversal Protection**
|
|
25
|
-
- `Utils.validate_file_path!` prevents `..` sequences and validates against allowed directories
|
|
26
|
-
- File paths are restricted to configured allowed directories
|
|
27
|
-
|
|
28
|
-
**✅ Signed Request Objects (RFC 9101)**
|
|
29
|
-
- All authorization requests **MUST** use signed request objects (mandatory per OpenID Federation 1.0 spec)
|
|
30
|
-
- Signing is enforced at library level - cannot be bypassed
|
|
31
|
-
- Request objects **MAY** be encrypted (optional) when provider requires it or `always_encrypt_request_object` is enabled
|
|
32
|
-
- Prevents parameter tampering and ensures request authenticity
|
|
33
|
-
|
|
34
|
-
**✅ JWT Algorithm Validation**
|
|
35
|
-
- JWT library validates algorithms and signatures
|
|
36
|
-
- Unsigned tokens (`alg: none`) are explicitly handled and rejected for signed tokens
|
|
37
|
-
- Only strong algorithms accepted (RS256, etc.)
|
|
38
|
-
|
|
39
|
-
**✅ Logging Sanitization**
|
|
40
|
-
- Sensitive data (tokens, keys) is never logged
|
|
41
|
-
- File paths are sanitized before logging
|
|
42
|
-
- URLs are sanitized (query parameters removed) in debug logs
|
|
43
|
-
|
|
44
|
-
**✅ Timeout Limits**
|
|
45
|
-
- HTTP requests have configurable timeouts to prevent long-running requests
|
|
46
|
-
|
|
47
|
-
### Known Limitations & Risks
|
|
48
|
-
|
|
49
|
-
**⚠️ SSRF Risk (Server-Side Request Forgery)**
|
|
50
|
-
- The library fetches entity statements and JWKS from URLs without validating against internal network access
|
|
51
|
-
- **Risk:** URLs could target localhost, private IPs, or cloud metadata endpoints
|
|
52
|
-
- **Mitigation:** Application should validate URLs before passing to library, or implement URL validation in library configuration
|
|
53
|
-
|
|
54
|
-
**⚠️ Memory Safety**
|
|
55
|
-
- Ruby does not provide secure memory management for sensitive data
|
|
56
|
-
- Private keys may persist in memory until garbage collection
|
|
57
|
-
- Memory dumps may contain key material
|
|
58
|
-
- **Mitigation:** Use secure environments and access controls
|
|
59
|
-
|
|
60
|
-
**⚠️ SSL/TLS Verification**
|
|
61
|
-
- SSL verification is automatically disabled in Rails development mode
|
|
62
|
-
- **Production:** Application must ensure SSL verification is enabled
|
|
63
|
-
|
|
64
|
-
**⚠️ Entity Statement Validation**
|
|
65
|
-
- Fingerprint validation is optional - skipping validation may allow malicious entity statements
|
|
66
|
-
- **Recommendation:** Always validate entity statement fingerprints when fetching from untrusted sources
|
|
67
|
-
|
|
68
|
-
**⚠️ Key Rotation Window**
|
|
69
|
-
- Brief window (up to cache TTL, default 24 hours) where rotated keys might not be immediately available
|
|
70
|
-
- Library handles this with retry logic, but applications should monitor rotation events
|
|
71
|
-
|
|
72
|
-
## Input/Output Points
|
|
73
|
-
|
|
74
|
-
### Input Points (Library Handles)
|
|
75
|
-
|
|
76
|
-
1. **OAuth Callback Parameters** (`code`, `state`, `error`)
|
|
77
|
-
- State validated with constant-time comparison
|
|
78
|
-
- Authorization codes are single-use and time-limited
|
|
79
|
-
|
|
80
|
-
2. **OAuth Request Parameters** (`acr_values`, `login_hint`, etc.)
|
|
81
|
-
- All parameters are signed in JWT request objects (RFC 9101)
|
|
82
|
-
- Parameters validated before inclusion
|
|
83
|
-
|
|
84
|
-
3. **Entity Statement URLs**
|
|
85
|
-
- Fetched via HTTP client
|
|
86
|
-
- **SSRF Risk:** No validation against internal network access
|
|
87
|
-
|
|
88
|
-
4. **File Paths** (configuration)
|
|
89
|
-
- Validated with `Utils.validate_file_path!` to prevent path traversal
|
|
90
|
-
- Restricted to allowed directories
|
|
91
|
-
|
|
92
|
-
5. **JWT/JWE Tokens** (from OAuth provider)
|
|
93
|
-
- Algorithm validation enforced
|
|
94
|
-
- Signature validation required for signed tokens
|
|
95
|
-
- Entity statement fingerprints provide additional validation
|
|
96
|
-
|
|
97
|
-
### Output Points (Library Exposes)
|
|
98
|
-
|
|
99
|
-
1. **HTTP Responses** (if using `RackEndpoint`)
|
|
100
|
-
- `/.well-known/openid-federation` - Entity statement (JWT)
|
|
101
|
-
- `/.well-known/jwks.json` - Public JWKS
|
|
102
|
-
- `/.well-known/signed-jwks.json` - Signed JWKS (JWT)
|
|
103
|
-
- Only public keys exposed (private keys never exposed)
|
|
104
|
-
|
|
105
|
-
2. **Logs**
|
|
106
|
-
- Sensitive data (tokens, keys) never logged
|
|
107
|
-
- File paths and URLs sanitized before logging
|
|
108
|
-
|
|
109
|
-
3. **Error Messages**
|
|
110
|
-
- File paths sanitized in error messages
|
|
111
|
-
- Generic error messages (stack traces only in development)
|
|
112
|
-
|
|
113
|
-
## Security Considerations
|
|
114
|
-
|
|
115
|
-
- **Timing Attacks**: State parameter protected with constant-time comparison
|
|
116
|
-
- **Path Traversal**: ✅ `Utils.validate_file_path!` prevents `..` sequences
|
|
117
|
-
- **JWT Algorithm Confusion**: ✅ Algorithm validation enforced, `alg: none` rejected
|
|
118
|
-
- **Replay Attacks**: Authorization codes are single-use and time-limited
|
|
19
|
+
- We will work with you to understand and resolve the issue
|
|
20
|
+
- We will credit you for the discovery (unless you prefer to remain anonymous)
|
|
21
|
+
- We will publish a security advisory after the vulnerability is patched
|
|
22
|
+
- We will coordinate public disclosure with you
|
|
119
23
|
|
|
120
24
|
## Automation Security
|
|
121
25
|
|
|
122
26
|
* **Context Isolation:** It is strictly forbidden to include production credentials, API keys, or Personally Identifiable Information (PII) in prompts sent to third-party LLMs or automation services.
|
|
123
27
|
|
|
124
28
|
* **Supply Chain:** All automated dependencies must be verified.
|
|
125
|
-
|
|
126
|
-
## Contact
|
|
127
|
-
|
|
128
|
-
**Security concerns**: contact@kiskolabs.com
|
|
129
|
-
**General support**: https://github.com/amkisko/omniauth_openid_federation.rb/issues
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
# Copy this to app/controllers/users/omniauth_callbacks_controller.rb
|
|
3
3
|
|
|
4
4
|
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|
5
|
+
# Skip Rails CSRF protection for OAuth callbacks
|
|
6
|
+
# OAuth callbacks from external providers cannot include Rails CSRF tokens
|
|
7
|
+
# CSRF protection is handled by OAuth state parameter validation in the strategy
|
|
8
|
+
skip_before_action :verify_authenticity_token, only: [:openid_federation, :failure]
|
|
5
9
|
skip_before_action :authenticate_user!, only: [:openid_federation, :failure]
|
|
6
10
|
|
|
7
11
|
def openid_federation
|
|
@@ -72,6 +72,40 @@ OmniauthOpenidFederation::EndpointResolver.validate_and_build_audience(
|
|
|
72
72
|
Devise.setup do |config|
|
|
73
73
|
# ... your other Devise configuration ...
|
|
74
74
|
|
|
75
|
+
# OmniAuth 2.0+ defaults to POST only for CSRF protection (CVE-2015-9284)
|
|
76
|
+
# Always use POST for security - forms must include CSRF token
|
|
77
|
+
if defined?(OmniAuth)
|
|
78
|
+
OmniAuth.config.allowed_request_methods = [:post]
|
|
79
|
+
OmniAuth.config.silence_get_warning = false
|
|
80
|
+
|
|
81
|
+
# Configure CSRF validation to check tokens only for request phase (initiating OAuth)
|
|
82
|
+
# Callback phase uses OAuth state parameter for CSRF protection (validated in strategy)
|
|
83
|
+
# This ensures:
|
|
84
|
+
# - Request phase: Forms must include Rails CSRF tokens (standard Rails protection)
|
|
85
|
+
# - Callback phase: OAuth state parameter provides CSRF protection (external providers can't include Rails tokens)
|
|
86
|
+
OmniAuth.config.request_validation_phase = lambda do |env|
|
|
87
|
+
request = Rack::Request.new(env)
|
|
88
|
+
path = request.path
|
|
89
|
+
|
|
90
|
+
# Skip CSRF validation for callback paths (external providers can't include Rails CSRF tokens)
|
|
91
|
+
# OAuth state parameter provides CSRF protection for callbacks (validated in OpenIDFederation strategy)
|
|
92
|
+
return true if path.end_with?("/callback")
|
|
93
|
+
|
|
94
|
+
# For request phase, use Rails' standard CSRF token validation
|
|
95
|
+
# This ensures forms must include valid CSRF tokens when initiating OAuth
|
|
96
|
+
session = env["rack.session"] || {}
|
|
97
|
+
token = request.params["authenticity_token"] || request.get_header("X-CSRF-Token")
|
|
98
|
+
expected_token = session[:_csrf_token] || session["_csrf_token"]
|
|
99
|
+
|
|
100
|
+
# Validate CSRF token using constant-time comparison
|
|
101
|
+
if token.present? && expected_token.present?
|
|
102
|
+
ActiveSupport::SecurityUtils.secure_compare(token.to_s, expected_token.to_s)
|
|
103
|
+
else
|
|
104
|
+
false
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
75
109
|
config.omniauth :openid_federation,
|
|
76
110
|
name: :openid_federation,
|
|
77
111
|
scope: [:openid],
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Rails Engine for OpenID Federation endpoints
|
|
2
|
+
# Provides controllers and routes for well-known OpenID Federation endpoints
|
|
3
|
+
#
|
|
4
|
+
# @see https://guides.rubyonrails.org/engines.html Rails Engines Guide
|
|
5
|
+
module OmniauthOpenidFederation
|
|
6
|
+
class Engine < ::Rails::Engine
|
|
7
|
+
# Don't isolate namespace because we need routes at specific well-known paths
|
|
8
|
+
# (/.well-known/openid-federation) rather than under a mount point
|
|
9
|
+
# isolate_namespace OmniauthOpenidFederation
|
|
10
|
+
|
|
11
|
+
# Explicitly require the controller to avoid Zeitwerk conflicts
|
|
12
|
+
# For local path gems, autoload_once_paths can cause conflicts with main app's loader
|
|
13
|
+
# We require the controller explicitly in to_prepare to ensure it's available for routing
|
|
14
|
+
config.to_prepare do
|
|
15
|
+
# Use self.class to access Engine class methods (root is a class method)
|
|
16
|
+
engine_root = OmniauthOpenidFederation::Engine.root
|
|
17
|
+
controller_path = engine_root.join("app", "controllers", "omniauth_openid_federation", "federation_controller.rb")
|
|
18
|
+
require controller_path.to_s if controller_path.exist?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -115,6 +115,13 @@ module OmniauthOpenidFederation
|
|
|
115
115
|
# Log security error but return nil to maintain backward compatibility
|
|
116
116
|
Logger.warn("[EntityStatementReader] Security error: #{e.message}")
|
|
117
117
|
nil
|
|
118
|
+
rescue Errno::EACCES, Errno::EISDIR, Errno::ENOENT
|
|
119
|
+
# Handle file system errors gracefully to avoid exposing file system structure
|
|
120
|
+
# EACCES: Permission denied
|
|
121
|
+
# EISDIR: Is a directory
|
|
122
|
+
# ENOENT: No such file or directory (race condition after File.exist?)
|
|
123
|
+
Logger.warn("[EntityStatementReader] File access error: #{Utils.sanitize_path(entity_statement_path)}")
|
|
124
|
+
nil
|
|
118
125
|
end
|
|
119
126
|
end
|
|
120
127
|
end
|
|
@@ -393,8 +393,8 @@ module OmniauthOpenidFederation
|
|
|
393
393
|
if unknown_claims.any?
|
|
394
394
|
# For now, we'll log a warning but not reject
|
|
395
395
|
# In a strict implementation, we should reject if we don't understand the claims
|
|
396
|
+
# Future enhancement: Add strict_mode option to reject unknown crit claims
|
|
396
397
|
OmniauthOpenidFederation::Logger.warn("[EntityStatementValidator] Entity statement contains crit claim with unknown claims: #{unknown_claims.join(", ")}. These claims MUST be understood and processed.")
|
|
397
|
-
# TODO: Make this configurable - strict mode should reject unknown crit claims
|
|
398
398
|
end
|
|
399
399
|
end
|
|
400
400
|
|
|
@@ -547,22 +547,21 @@ module OmniauthOpenidFederation
|
|
|
547
547
|
|
|
548
548
|
# Mount the federation endpoint routes in Rails routes
|
|
549
549
|
#
|
|
550
|
-
#
|
|
550
|
+
# RECOMMENDED: Use the Engine (Rails-idiomatic way):
|
|
551
551
|
# Rails.application.routes.draw do
|
|
552
|
-
# OmniauthOpenidFederation::
|
|
552
|
+
# mount OmniauthOpenidFederation::Engine => "/"
|
|
553
553
|
# end
|
|
554
554
|
#
|
|
555
|
-
# This mounts all four endpoints:
|
|
555
|
+
# This mounts all four endpoints at the root level:
|
|
556
556
|
# - GET /.well-known/openid-federation (entity statement)
|
|
557
557
|
# - GET /.well-known/openid-federation/fetch (fetch endpoint for Subordinate Statements)
|
|
558
558
|
# - GET /.well-known/jwks.json (standard JWKS)
|
|
559
559
|
# - GET /.well-known/signed-jwks.json (signed JWKS)
|
|
560
560
|
#
|
|
561
|
-
#
|
|
562
|
-
#
|
|
563
|
-
#
|
|
564
|
-
#
|
|
565
|
-
# get "/.well-known/signed-jwks.json", to: "omniauth_openid_federation/federation#signed_jwks"
|
|
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
|
|
566
565
|
#
|
|
567
566
|
# @param router [ActionDispatch::Routing::Mapper] The routes mapper (pass `self` from routes.rb)
|
|
568
567
|
# @param entity_statement_path [String] Path for entity statement endpoint (default: "/.well-known/openid-federation")
|
|
@@ -570,6 +569,7 @@ module OmniauthOpenidFederation
|
|
|
570
569
|
# @param jwks_path [String] Path for standard JWKS endpoint (default: "/.well-known/jwks.json")
|
|
571
570
|
# @param signed_jwks_path [String] Path for signed JWKS endpoint (default: "/.well-known/signed-jwks.json")
|
|
572
571
|
# @param as [String, Symbol] Route name prefix (default: :openid_federation)
|
|
572
|
+
# @deprecated Use `mount OmniauthOpenidFederation::Engine => "/"` instead (Rails-idiomatic way)
|
|
573
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
574
|
# Controller uses Rails-conventional naming (OmniauthOpenidFederation)
|
|
575
575
|
# which matches natural inflection from omniauth_openid_federation
|
|
@@ -46,6 +46,7 @@ module OmniauthOpenidFederation
|
|
|
46
46
|
EVENT_ISSUER_MISMATCH = "issuer_mismatch"
|
|
47
47
|
EVENT_EXPIRED_TOKEN = "expired_token"
|
|
48
48
|
EVENT_INVALID_NONCE = "invalid_nonce"
|
|
49
|
+
EVENT_AUTHENTICITY_ERROR = "authenticity_error"
|
|
49
50
|
|
|
50
51
|
class << self
|
|
51
52
|
# Notify about a security event
|
|
@@ -348,6 +349,21 @@ module OmniauthOpenidFederation
|
|
|
348
349
|
)
|
|
349
350
|
end
|
|
350
351
|
|
|
352
|
+
# Notify about authenticity token error (OmniAuth CSRF protection)
|
|
353
|
+
#
|
|
354
|
+
# @param data [Hash] Additional context (error_type, error_message, phase, request_info)
|
|
355
|
+
# @return [void]
|
|
356
|
+
def notify_authenticity_error(data = {})
|
|
357
|
+
notify(
|
|
358
|
+
EVENT_AUTHENTICITY_ERROR,
|
|
359
|
+
data: {
|
|
360
|
+
reason: "OmniAuth authenticity token validation failed - CSRF protection blocked request",
|
|
361
|
+
**data
|
|
362
|
+
},
|
|
363
|
+
severity: :error
|
|
364
|
+
)
|
|
365
|
+
end
|
|
366
|
+
|
|
351
367
|
private
|
|
352
368
|
|
|
353
369
|
# Sanitize data to remove sensitive information
|
|
@@ -165,8 +165,9 @@ module OmniauthOpenidFederation
|
|
|
165
165
|
# @return [Array<Hash>] Array with [payload, header]
|
|
166
166
|
# @raise [ValidationError] If JWT validation fails
|
|
167
167
|
# @raise [SignatureError] If signature verification fails
|
|
168
|
-
# @deprecated Use jwt() method instead
|
|
168
|
+
# @deprecated Use jwt() method instead. This method will be removed in a future version.
|
|
169
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.")
|
|
170
171
|
jwt(encoded_jwt, jwks_uri, retried: retried, entity_statement_keys: entity_statement_keys)
|
|
171
172
|
end
|
|
172
173
|
end
|
|
@@ -65,7 +65,7 @@ module OmniauthOpenidFederation
|
|
|
65
65
|
attr_accessor :private_key, :state, :nonce
|
|
66
66
|
# Provider-specific extension parameters (outside JWT)
|
|
67
67
|
# Some providers may require additional parameters that are not part of the JWT
|
|
68
|
-
# @deprecated Use
|
|
68
|
+
# @deprecated Use request_object_params option in strategy instead (adds params to the JWT request object)
|
|
69
69
|
attr_accessor :ftn_spname
|
|
70
70
|
|
|
71
71
|
# Initialize JWT request object builder
|
|
@@ -256,12 +256,6 @@ module OmniauthOpenidFederation
|
|
|
256
256
|
JWT.encode(claim, @private_key, "RS256", header)
|
|
257
257
|
end
|
|
258
258
|
|
|
259
|
-
def load_signing_key
|
|
260
|
-
# Deprecated: Use KeyExtractor.extract_signing_key instead
|
|
261
|
-
# This method is kept for backward compatibility but should not be used
|
|
262
|
-
nil
|
|
263
|
-
end
|
|
264
|
-
|
|
265
259
|
def signing_key_kid
|
|
266
260
|
metadata = load_metadata_from_entity_statement
|
|
267
261
|
return nil unless metadata
|
|
@@ -1,26 +1,12 @@
|
|
|
1
|
-
# Railtie to load rake tasks
|
|
1
|
+
# Railtie to load rake tasks
|
|
2
|
+
# Note: Controllers and routes are now handled by the Engine (lib/omniauth_openid_federation/engine.rb)
|
|
3
|
+
# This Railtie is kept for backward compatibility and for loading rake tasks
|
|
2
4
|
if defined?(Rails)
|
|
3
5
|
module OmniauthOpenidFederation
|
|
4
6
|
class Railtie < Rails::Railtie
|
|
5
|
-
# Add gem's controllers to autoload paths
|
|
6
|
-
# This ensures the controller can be found by Rails routing
|
|
7
|
-
initializer "omniauth_openid_federation.add_autoload_paths", before: :set_autoload_paths do |app|
|
|
8
|
-
controllers_path = File.join(File.dirname(__FILE__), "..", "..", "app", "controllers")
|
|
9
|
-
app.config.autoload_once_paths << controllers_path if File.exist?(controllers_path)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# Load controller when Rails is available (for development reloading)
|
|
13
|
-
config.to_prepare do
|
|
14
|
-
controller_path = File.join(File.dirname(__FILE__), "..", "..", "app", "controllers", "omniauth_openid_federation", "federation_controller.rb")
|
|
15
|
-
require controller_path if File.exist?(controller_path)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
7
|
rake_tasks do
|
|
19
8
|
# Load rake tasks from lib/tasks
|
|
20
9
|
# Rails automatically loads lib/tasks/**/*.rake, but we ensure they're loaded here too
|
|
21
|
-
# File.dirname(__FILE__) = lib/omniauth_openid_federation
|
|
22
|
-
# .. = lib
|
|
23
|
-
# tasks = lib/tasks
|
|
24
10
|
task_files = Dir[File.join(File.dirname(__FILE__), "..", "tasks", "**", "*.rake")]
|
|
25
11
|
task_files.each { |task_file| load task_file } if task_files.any?
|
|
26
12
|
end
|
|
@@ -186,6 +186,83 @@ module OmniAuth
|
|
|
186
186
|
client
|
|
187
187
|
end
|
|
188
188
|
|
|
189
|
+
# Override fail! to instrument all authentication failures
|
|
190
|
+
# This catches failures from OmniAuth middleware (like AuthenticityTokenProtection)
|
|
191
|
+
# as well as failures from within the strategy
|
|
192
|
+
#
|
|
193
|
+
# @param error_type [Symbol] Error type identifier
|
|
194
|
+
# @param exception [Exception] Exception object
|
|
195
|
+
# @return [void]
|
|
196
|
+
def fail!(error_type, exception = nil)
|
|
197
|
+
# Determine if this error has already been instrumented
|
|
198
|
+
# Errors instrumented before calling fail! will have a flag set
|
|
199
|
+
already_instrumented = env["omniauth_openid_federation.instrumented"] == true
|
|
200
|
+
|
|
201
|
+
unless already_instrumented
|
|
202
|
+
# Extract error information
|
|
203
|
+
error_message = exception&.message || error_type.to_s
|
|
204
|
+
error_class = exception&.class&.name || "UnknownError"
|
|
205
|
+
|
|
206
|
+
# Determine the phase (request or callback)
|
|
207
|
+
phase = request.path.end_with?("/callback") ? "callback_phase" : "request_phase"
|
|
208
|
+
|
|
209
|
+
# Build request info
|
|
210
|
+
request_info = {
|
|
211
|
+
remote_ip: request.env["REMOTE_ADDR"],
|
|
212
|
+
user_agent: request.env["HTTP_USER_AGENT"],
|
|
213
|
+
path: request.path,
|
|
214
|
+
method: request.request_method
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# Instrument based on error type
|
|
218
|
+
case error_type.to_sym
|
|
219
|
+
when :authenticity_error
|
|
220
|
+
# OmniAuth CSRF protection error (from middleware)
|
|
221
|
+
OmniauthOpenidFederation::Instrumentation.notify_authenticity_error(
|
|
222
|
+
error_type: error_type.to_s,
|
|
223
|
+
error_message: error_message,
|
|
224
|
+
error_class: error_class,
|
|
225
|
+
phase: phase,
|
|
226
|
+
request_info: request_info
|
|
227
|
+
)
|
|
228
|
+
when :csrf_detected
|
|
229
|
+
# This should already be instrumented before calling fail!, but instrument here as fallback
|
|
230
|
+
# (e.g., if fail! is called directly without prior instrumentation)
|
|
231
|
+
OmniauthOpenidFederation::Instrumentation.notify_csrf_detected(
|
|
232
|
+
error_type: error_type.to_s,
|
|
233
|
+
error_message: error_message,
|
|
234
|
+
phase: phase,
|
|
235
|
+
request_info: request_info
|
|
236
|
+
)
|
|
237
|
+
when :missing_code, :token_exchange_error
|
|
238
|
+
# These should already be instrumented before calling fail!, but instrument here as fallback
|
|
239
|
+
# (e.g., if fail! is called directly without prior instrumentation)
|
|
240
|
+
OmniauthOpenidFederation::Instrumentation.notify_unexpected_authentication_break(
|
|
241
|
+
stage: phase,
|
|
242
|
+
error_message: error_message,
|
|
243
|
+
error_class: error_class,
|
|
244
|
+
error_type: error_type.to_s,
|
|
245
|
+
request_info: request_info
|
|
246
|
+
)
|
|
247
|
+
else
|
|
248
|
+
# Unknown error type - instrument as unexpected authentication break
|
|
249
|
+
OmniauthOpenidFederation::Instrumentation.notify_unexpected_authentication_break(
|
|
250
|
+
stage: phase,
|
|
251
|
+
error_message: error_message,
|
|
252
|
+
error_class: error_class,
|
|
253
|
+
error_type: error_type.to_s,
|
|
254
|
+
request_info: request_info
|
|
255
|
+
)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Mark as instrumented to prevent double instrumentation
|
|
260
|
+
env["omniauth_openid_federation.instrumented"] = true
|
|
261
|
+
|
|
262
|
+
# Call parent fail! method
|
|
263
|
+
super
|
|
264
|
+
end
|
|
265
|
+
|
|
189
266
|
# Override request_phase to use our custom authorize_uri instead of client.auth_code
|
|
190
267
|
# The base OAuth2 strategy calls client.auth_code.authorize_url, but OpenIDConnect::Client
|
|
191
268
|
# doesn't have an auth_code method - it uses authorization_uri directly
|
|
@@ -218,6 +295,8 @@ module OmniAuth
|
|
|
218
295
|
path: request.path
|
|
219
296
|
}
|
|
220
297
|
)
|
|
298
|
+
# Mark as instrumented to prevent double instrumentation in fail!
|
|
299
|
+
env["omniauth_openid_federation.instrumented"] = true
|
|
221
300
|
fail!(:csrf_detected, OmniauthOpenidFederation::SecurityError.new("CSRF detected"))
|
|
222
301
|
return
|
|
223
302
|
end
|
|
@@ -238,6 +317,8 @@ module OmniAuth
|
|
|
238
317
|
path: request.path
|
|
239
318
|
}
|
|
240
319
|
)
|
|
320
|
+
# Mark as instrumented to prevent double instrumentation in fail!
|
|
321
|
+
env["omniauth_openid_federation.instrumented"] = true
|
|
241
322
|
fail!(:missing_code, OmniauthOpenidFederation::ValidationError.new("Missing authorization code"))
|
|
242
323
|
return
|
|
243
324
|
end
|
|
@@ -258,6 +339,8 @@ module OmniAuth
|
|
|
258
339
|
path: request.path
|
|
259
340
|
}
|
|
260
341
|
)
|
|
342
|
+
# Mark as instrumented to prevent double instrumentation in fail!
|
|
343
|
+
env["omniauth_openid_federation.instrumented"] = true
|
|
261
344
|
fail!(:token_exchange_error, e)
|
|
262
345
|
return
|
|
263
346
|
end
|
|
@@ -419,13 +502,15 @@ module OmniAuth
|
|
|
419
502
|
|
|
420
503
|
# Add provider-specific extension parameters if configured
|
|
421
504
|
# Note: Some providers may require additional parameters outside the JWT
|
|
422
|
-
#
|
|
505
|
+
# @deprecated ftn_spname option - Use request_object_params instead for adding params to the JWT request object
|
|
423
506
|
if options.ftn_spname && !options.ftn_spname.to_s.empty?
|
|
507
|
+
OmniauthOpenidFederation::Logger.warn("[Strategy] ftn_spname option is deprecated. Use request_object_params: ['ftn_spname'] instead.")
|
|
424
508
|
jws_builder.ftn_spname = options.ftn_spname
|
|
425
509
|
end
|
|
426
510
|
|
|
427
|
-
# Allow dynamic
|
|
428
|
-
|
|
511
|
+
# Allow dynamic request object params from HTTP request if configured
|
|
512
|
+
# These parameters are added as claims to the JWT request object (RFC 9101)
|
|
513
|
+
options.request_object_params&.each do |key|
|
|
429
514
|
value = request_params[key.to_s]
|
|
430
515
|
jws_builder.add_claim(key.to_sym, value) if value && !value.to_s.empty?
|
|
431
516
|
end
|
|
@@ -470,7 +555,7 @@ module OmniAuth
|
|
|
470
555
|
|
|
471
556
|
# Add provider-specific extension parameters outside JWT if configured
|
|
472
557
|
# These are allowed per provider requirements (some providers require additional parameters)
|
|
473
|
-
#
|
|
558
|
+
# @deprecated ftn_spname option - Use request_object_params instead for adding params to the JWT request object
|
|
474
559
|
if options.ftn_spname && !options.ftn_spname.to_s.empty?
|
|
475
560
|
query_params[:ftn_spname] = options.ftn_spname
|
|
476
561
|
end
|
|
@@ -83,6 +83,8 @@ module OmniauthOpenidFederation
|
|
|
83
83
|
resolved = File.expand_path(path_str)
|
|
84
84
|
|
|
85
85
|
# Validate it's within allowed directories if specified
|
|
86
|
+
# When allowed_dirs is nil, we trust the developer to pass appropriate paths
|
|
87
|
+
# Path traversal protection (.. and ~) is still enforced above
|
|
86
88
|
if allowed_dirs && !allowed_dirs.empty?
|
|
87
89
|
allowed = allowed_dirs.any? do |dir|
|
|
88
90
|
expanded_dir = File.expand_path(dir)
|
|
@@ -82,8 +82,9 @@ module OmniauthOpenidFederation
|
|
|
82
82
|
end
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
-
# Load
|
|
85
|
+
# Load Engine for Rails integration (controllers, routes, etc.)
|
|
86
86
|
if defined?(Rails)
|
|
87
|
+
require_relative "omniauth_openid_federation/engine"
|
|
87
88
|
require_relative "omniauth_openid_federation/railtie"
|
|
88
89
|
end
|
|
89
90
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omniauth_openid_federation
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -83,16 +83,22 @@ dependencies:
|
|
|
83
83
|
name: rack
|
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
|
85
85
|
requirements:
|
|
86
|
-
- - "
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '2.0'
|
|
89
|
+
- - "<"
|
|
87
90
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: '
|
|
91
|
+
version: '4'
|
|
89
92
|
type: :runtime
|
|
90
93
|
prerelease: false
|
|
91
94
|
version_requirements: !ruby/object:Gem::Requirement
|
|
92
95
|
requirements:
|
|
93
|
-
- - "
|
|
96
|
+
- - ">="
|
|
97
|
+
- !ruby/object:Gem::Version
|
|
98
|
+
version: '2.0'
|
|
99
|
+
- - "<"
|
|
94
100
|
- !ruby/object:Gem::Version
|
|
95
|
-
version: '
|
|
101
|
+
version: '4'
|
|
96
102
|
- !ruby/object:Gem::Dependency
|
|
97
103
|
name: rspec
|
|
98
104
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -285,6 +291,7 @@ files:
|
|
|
285
291
|
- lib/omniauth_openid_federation/configuration.rb
|
|
286
292
|
- lib/omniauth_openid_federation/constants.rb
|
|
287
293
|
- lib/omniauth_openid_federation/endpoint_resolver.rb
|
|
294
|
+
- lib/omniauth_openid_federation/engine.rb
|
|
288
295
|
- lib/omniauth_openid_federation/entity_statement_reader.rb
|
|
289
296
|
- lib/omniauth_openid_federation/errors.rb
|
|
290
297
|
- lib/omniauth_openid_federation/federation/entity_statement.rb
|