omniauth_openid_federation 1.3.2 → 2.1.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -1
  3. data/README.md +136 -15
  4. data/SECURITY.md +11 -14
  5. data/app/controllers/omniauth_openid_federation/federation_controller.rb +2 -2
  6. data/config/polyrun_coverage.yml +15 -0
  7. data/config/polyrun_spec_quality.yml +29 -0
  8. data/examples/README_INTEGRATION_TESTING.md +3 -0
  9. data/examples/integration_test_flow.rb +10 -8
  10. data/examples/jobs/federation_cache_refresh_job.rb.example +7 -7
  11. data/examples/jobs/federation_files_generation_job.rb.example +3 -2
  12. data/examples/mock_op_server.rb +4 -5
  13. data/examples/mock_rp_server.rb +3 -3
  14. data/examples/standalone_multiple_endpoints_example.rb +494 -0
  15. data/lib/omniauth_openid_federation/access_token.rb +91 -450
  16. data/lib/omniauth_openid_federation/cache.rb +42 -4
  17. data/lib/omniauth_openid_federation/cache_adapter.rb +6 -3
  18. data/lib/omniauth_openid_federation/constants.rb +6 -0
  19. data/lib/omniauth_openid_federation/federation/entity_statement.rb +0 -4
  20. data/lib/omniauth_openid_federation/federation/entity_statement_validator.rb +33 -21
  21. data/lib/omniauth_openid_federation/federation/signed_jwks.rb +4 -2
  22. data/lib/omniauth_openid_federation/federation/trust_chain_resolver.rb +85 -20
  23. data/lib/omniauth_openid_federation/federation/trust_chain_signature.rb +62 -0
  24. data/lib/omniauth_openid_federation/federation_endpoint.rb +1 -1
  25. data/lib/omniauth_openid_federation/http_client.rb +182 -32
  26. data/lib/omniauth_openid_federation/http_errors.rb +14 -0
  27. data/lib/omniauth_openid_federation/id_token.rb +19 -0
  28. data/lib/omniauth_openid_federation/jwe.rb +26 -0
  29. data/lib/omniauth_openid_federation/jwks/decode.rb +0 -13
  30. data/lib/omniauth_openid_federation/jwks/fetch.rb +16 -39
  31. data/lib/omniauth_openid_federation/jws.rb +2 -11
  32. data/lib/omniauth_openid_federation/jwt_response_decoder.rb +288 -0
  33. data/lib/omniauth_openid_federation/oidc_client.rb +101 -0
  34. data/lib/omniauth_openid_federation/rack.rb +2 -0
  35. data/lib/omniauth_openid_federation/rack_endpoint.rb +2 -2
  36. data/lib/omniauth_openid_federation/rate_limiter.rb +10 -12
  37. data/lib/omniauth_openid_federation/secure_compare.rb +15 -0
  38. data/lib/omniauth_openid_federation/strategy/authorization_request.rb +220 -0
  39. data/lib/omniauth_openid_federation/strategy/callback_handling.rb +133 -0
  40. data/lib/omniauth_openid_federation/strategy/client_construction.rb +101 -0
  41. data/lib/omniauth_openid_federation/strategy/client_entity_statement.rb +216 -0
  42. data/lib/omniauth_openid_federation/strategy/endpoint_resolution.rb +432 -0
  43. data/lib/omniauth_openid_federation/strategy/failure_handling.rb +75 -0
  44. data/lib/omniauth_openid_federation/strategy/id_token_decoding.rb +268 -0
  45. data/lib/omniauth_openid_federation/strategy/jwks_resolution.rb +262 -0
  46. data/lib/omniauth_openid_federation/strategy/provider_entity_statement.rb +135 -0
  47. data/lib/omniauth_openid_federation/strategy/userinfo_decoding.rb +93 -0
  48. data/lib/omniauth_openid_federation/strategy.rb +30 -1911
  49. data/lib/omniauth_openid_federation/tasks/authentication_flow_tester.rb +237 -0
  50. data/lib/omniauth_openid_federation/tasks/callback_processor.rb +235 -0
  51. data/lib/omniauth_openid_federation/tasks/client_keys_preparer.rb +96 -0
  52. data/lib/omniauth_openid_federation/tasks/local_endpoint_tester.rb +189 -0
  53. data/lib/omniauth_openid_federation/tasks/path_resolver.rb +20 -0
  54. data/lib/omniauth_openid_federation/tasks_helper.rb +28 -808
  55. data/lib/omniauth_openid_federation/time_helpers.rb +7 -0
  56. data/lib/omniauth_openid_federation/user_info.rb +15 -0
  57. data/lib/omniauth_openid_federation/utils.rb +11 -3
  58. data/lib/omniauth_openid_federation/validators.rb +43 -8
  59. data/lib/omniauth_openid_federation/version.rb +1 -1
  60. data/lib/omniauth_openid_federation.rb +9 -3
  61. data/lib/tasks/omniauth_openid_federation.rake +1 -2
  62. data/sig/omniauth_openid_federation.rbs +2 -1
  63. data/sig/strategy.rbs +6 -6
  64. metadata +188 -63
@@ -0,0 +1,93 @@
1
+ require_relative "../secure_compare"
2
+ require_relative "../string_helpers"
3
+
4
+ module OmniauthOpenidFederation
5
+ module Strategy
6
+ module UserinfoDecoding
7
+ IDENTITY_CLAIM_KEYS = %w[iss sub aud exp iat].freeze
8
+
9
+ private
10
+
11
+ def merge_userinfo_with_id_token_claims(id_token_claims, userinfo_hash)
12
+ normalized_id = stringify_claim_keys(id_token_claims)
13
+ normalized_userinfo = stringify_claim_keys(userinfo_hash)
14
+
15
+ id_sub = normalized_id["sub"]
16
+ userinfo_sub = normalized_userinfo["sub"]
17
+
18
+ if OmniauthOpenidFederation::StringHelpers.blank?(userinfo_sub) ||
19
+ OmniauthOpenidFederation::StringHelpers.blank?(id_sub) ||
20
+ !OmniauthOpenidFederation::SecureCompare.secure_compare(userinfo_sub.to_s, id_sub.to_s)
21
+ raise OmniauthOpenidFederation::ValidationError,
22
+ "UserInfo sub must match ID token sub"
23
+ end
24
+
25
+ merged = normalized_id.merge(normalized_userinfo)
26
+ IDENTITY_CLAIM_KEYS.each do |claim|
27
+ merged[claim] = normalized_id[claim] if normalized_id.key?(claim)
28
+ end
29
+ merged
30
+ end
31
+
32
+ def stringify_claim_keys(claims)
33
+ (claims || {}).each_with_object({}) do |(key, value), hash|
34
+ hash[key.to_s] = value
35
+ end
36
+ end
37
+
38
+ def decode_userinfo(userinfo)
39
+ if userinfo.is_a?(String)
40
+ if encrypted_token?(userinfo)
41
+ client_options_hash = options.client_options || {}
42
+ normalized_options = OmniauthOpenidFederation::Validators.normalize_hash(client_options_hash)
43
+
44
+ decryption_key_source = options.decryption_key_source || options.key_source || :local
45
+ private_key = normalized_options[:private_key]
46
+ jwks = normalized_options[:jwks] || normalized_options["jwks"]
47
+ metadata = load_metadata_for_key_extraction
48
+
49
+ encryption_key = if decryption_key_source == :federation
50
+ OmniauthOpenidFederation::KeyExtractor.extract_encryption_key(
51
+ jwks: jwks,
52
+ metadata: metadata,
53
+ private_key: private_key
54
+ ) || private_key
55
+ else
56
+ private_key
57
+ end
58
+
59
+ OmniauthOpenidFederation::Validators.validate_private_key!(encryption_key)
60
+
61
+ begin
62
+ userinfo_string = OmniauthOpenidFederation::Jwe.decrypt(userinfo, encryption_key)
63
+ OmniauthOpenidFederation::Logger.debug("[Strategy] Successfully decrypted userinfo using encryption key")
64
+ JSON.parse(userinfo_string)
65
+ rescue => e
66
+ error_msg = "Failed to decrypt userinfo: #{e.class} - #{e.message}"
67
+ OmniauthOpenidFederation::Logger.error("[Strategy] #{error_msg}")
68
+ OmniauthOpenidFederation::Instrumentation.notify_decryption_failed(
69
+ token_type: "userinfo",
70
+ error_message: e.message,
71
+ error_class: e.class.name
72
+ )
73
+ raise OmniauthOpenidFederation::DecryptionError, error_msg, e.backtrace
74
+ end
75
+ else
76
+ JSON.parse(userinfo)
77
+ end
78
+ elsif userinfo.is_a?(Hash)
79
+ userinfo
80
+ elsif userinfo.respond_to?(:raw_attributes)
81
+ userinfo.raw_attributes || {}
82
+ elsif userinfo.respond_to?(:as_json)
83
+ userinfo.as_json(skip_validation: true)
84
+ else
85
+ userinfo.instance_variables.each_with_object({}) do |var, hash|
86
+ key = var.to_s.delete_prefix("@").to_sym
87
+ hash[key] = userinfo.instance_variable_get(var)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end