openid_connect 0.6.1 → 2.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.
Files changed (75) hide show
  1. checksums.yaml +5 -5
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/workflows/spec.yml +31 -0
  4. data/.gitignore +1 -0
  5. data/CHANGELOG.md +23 -0
  6. data/LICENSE +3 -1
  7. data/README.rdoc +10 -3
  8. data/Rakefile +6 -6
  9. data/TODOs +12 -0
  10. data/VERSION +1 -1
  11. data/lib/openid_connect/access_token/mtls.rb +9 -0
  12. data/lib/openid_connect/access_token.rb +14 -6
  13. data/lib/openid_connect/client/registrar.rb +69 -130
  14. data/lib/openid_connect/client.rb +7 -12
  15. data/lib/openid_connect/discovery/provider/config/resource.rb +5 -3
  16. data/lib/openid_connect/discovery/provider/config/response.rb +73 -78
  17. data/lib/openid_connect/discovery/provider/config.rb +5 -2
  18. data/lib/openid_connect/discovery/provider.rb +6 -2
  19. data/lib/openid_connect/discovery.rb +0 -2
  20. data/lib/openid_connect/jwtnizable.rb +6 -4
  21. data/lib/openid_connect/request_object/claimable.rb +4 -6
  22. data/lib/openid_connect/request_object.rb +6 -13
  23. data/lib/openid_connect/response_object/id_token.rb +38 -37
  24. data/lib/openid_connect/response_object/user_info/address.rb +10 -0
  25. data/lib/openid_connect/response_object/user_info.rb +64 -2
  26. data/lib/openid_connect.rb +26 -11
  27. data/lib/rack/oauth2/server/authorize/extension/code_and_id_token.rb +5 -1
  28. data/lib/rack/oauth2/server/authorize/extension/code_and_id_token_and_token.rb +1 -1
  29. data/lib/rack/oauth2/server/authorize/extension/id_token.rb +1 -1
  30. data/lib/rack/oauth2/server/authorize/extension/id_token_and_token.rb +1 -1
  31. data/lib/rack/oauth2/server/authorize/request_with_connect_params.rb +17 -14
  32. data/lib/rack/oauth2/server/id_token_response.rb +11 -13
  33. data/openid_connect.gemspec +19 -13
  34. data/spec/helpers/crypto_spec_helper.rb +2 -2
  35. data/spec/helpers/webmock_helper.rb +14 -9
  36. data/spec/mock_response/access_token/without_token_type.json +3 -0
  37. data/spec/mock_response/discovery/config.json +3 -3
  38. data/spec/mock_response/discovery/config_with_custom_port.json +13 -0
  39. data/spec/mock_response/discovery/config_with_invalid_issuer.json +13 -0
  40. data/spec/mock_response/discovery/config_with_path.json +13 -0
  41. data/spec/mock_response/discovery/config_without_issuer.json +12 -0
  42. data/spec/mock_response/errors/unknown.json +3 -1
  43. data/spec/mock_response/public_keys/{jwk.json → jwks.json} +1 -1
  44. data/spec/mock_response/public_keys/jwks_with_private_key.json +8 -0
  45. data/spec/mock_response/public_keys/private_key.pem +27 -0
  46. data/spec/openid_connect/access_token_spec.rb +11 -20
  47. data/spec/openid_connect/client/registrar_spec.rb +93 -208
  48. data/spec/openid_connect/client_spec.rb +79 -22
  49. data/spec/openid_connect/connect_object_spec.rb +1 -1
  50. data/spec/openid_connect/discovery/provider/config/response_spec.rb +76 -284
  51. data/spec/openid_connect/discovery/provider/config_spec.rb +64 -27
  52. data/spec/openid_connect/discovery/provider_spec.rb +2 -2
  53. data/spec/openid_connect/request_object_spec.rb +4 -4
  54. data/spec/openid_connect/response_object/id_token_spec.rb +94 -52
  55. data/spec/openid_connect/response_object/user_info/{open_id/address_spec.rb → address_spec.rb} +3 -3
  56. data/spec/openid_connect/response_object/{user_info/open_id_spec.rb → user_info_spec.rb} +13 -12
  57. data/spec/openid_connect_spec.rb +19 -19
  58. data/spec/rack/oauth2/server/authorize/extension/code_and_id_token_and_token_spec.rb +11 -0
  59. data/spec/rack/oauth2/server/authorize/extension/code_and_id_token_spec.rb +11 -0
  60. data/spec/rack/oauth2/server/authorize/extension/id_token_and_token_spec.rb +11 -0
  61. data/spec/rack/oauth2/server/authorize/extension/id_token_spec.rb +1 -1
  62. data/spec/rack/oauth2/server/authorize/request_with_connect_params_spec.rb +45 -0
  63. data/spec/spec_helper.rb +12 -1
  64. metadata +155 -90
  65. data/.travis.yml +0 -3
  66. data/Gemfile.lock +0 -102
  67. data/lib/openid_connect/debugger/request_filter.rb +0 -28
  68. data/lib/openid_connect/debugger.rb +0 -3
  69. data/lib/openid_connect/response_object/user_info/open_id/address.rb +0 -12
  70. data/lib/openid_connect/response_object/user_info/open_id.rb +0 -64
  71. data/lib/rack/oauth2/server/resource/error_with_connect_ext.rb +0 -14
  72. data/spec/mock_response/public_keys/x509.pem +0 -21
  73. data/spec/openid_connect/debugger/request_filter_spec.rb +0 -33
  74. data/spec/rack/oauth2/server/resource/error_with_connect_ext_spec.rb +0 -12
  75. /data/spec/mock_response/{user_info → userinfo}/openid.json +0 -0
@@ -2,116 +2,111 @@ module OpenIDConnect
2
2
  module Discovery
3
3
  module Provider
4
4
  class Config
5
- class Response < SWD::Resource
6
- include AttrOptional
5
+ class Response
6
+ include ActiveModel::Validations, AttrRequired, AttrOptional
7
7
 
8
+ cattr_accessor :metadata_attributes
8
9
  attr_reader :raw
9
- attr_optional(
10
- :version,
11
- :issuer,
12
- :authorization_endpoint,
13
- :token_endpoint,
14
- :userinfo_endpoint,
15
- :refresh_session_endpoint,
16
- :check_session_endpoint,
17
- :end_session_endpoint,
18
- :jwk_url,
19
- :jwk_encryption_url,
20
- :x509_url,
21
- :x509_encryption_url,
22
- :registration_endpoint,
23
- :scopes_supported,
10
+ attr_accessor :expected_issuer
11
+ uri_attributes = {
12
+ required: [
13
+ :issuer,
14
+ :authorization_endpoint,
15
+ :jwks_uri
16
+ ],
17
+ optional: [
18
+ :token_endpoint,
19
+ :userinfo_endpoint,
20
+ :registration_endpoint,
21
+ :end_session_endpoint,
22
+ :service_documentation,
23
+ :check_session_iframe,
24
+ :op_policy_uri,
25
+ :op_tos_uri
26
+ ]
27
+ }
28
+ attr_required(*(uri_attributes[:required] + [
24
29
  :response_types_supported,
25
- :acr_values_supported,
26
30
  :subject_types_supported,
27
- :claims_supported,
31
+ :id_token_signing_alg_values_supported
32
+ ]))
33
+ attr_optional(*(uri_attributes[:optional] + [
34
+ :scopes_supported,
35
+ :response_modes_supported,
36
+ :grant_types_supported,
37
+ :acr_values_supported,
38
+ :id_token_encryption_alg_values_supported,
39
+ :id_token_encryption_enc_values_supported,
28
40
  :userinfo_signing_alg_values_supported,
29
41
  :userinfo_encryption_alg_values_supported,
30
42
  :userinfo_encryption_enc_values_supported,
31
- :id_token_signing_alg_values_supported,
32
- :id_token_encryption_alg_values_supported,
33
- :id_token_encryption_enc_values_supported,
34
43
  :request_object_signing_alg_values_supported,
35
44
  :request_object_encryption_alg_values_supported,
36
45
  :request_object_encryption_enc_values_supported,
37
46
  :token_endpoint_auth_methods_supported,
38
- :token_endpoint_auth_signing_alg_values_supported
39
- )
40
- [
41
- :userinfo_endpoint,
42
- :userinfo_signing_alg_values_supported,
43
- :userinfo_encryption_alg_values_supported,
44
- :userinfo_encryption_enc_values_supported
45
- ].each do |userinfo_attribute|
46
- user_info_attribute = userinfo_attribute.to_s.sub('userinfo', 'user_info').to_sym
47
- alias_method user_info_attribute, userinfo_attribute
48
- alias_method :"#{user_info_attribute}=", userinfo_attribute
49
- end
47
+ :token_endpoint_auth_signing_alg_values_supported,
48
+ :display_values_supported,
49
+ :claim_types_supported,
50
+ :claims_supported,
51
+ :claims_locales_supported,
52
+ :ui_locales_supported,
53
+ :claims_parameter_supported,
54
+ :request_parameter_supported,
55
+ :request_uri_parameter_supported,
56
+ :require_request_uri_registration
57
+ ]))
58
+
59
+ validates(*required_attributes, presence: true)
60
+ validates(*uri_attributes.values.flatten, url: true, allow_nil: true)
61
+ validates :issuer, with: :validate_issuer_matching
50
62
 
51
63
  def initialize(hash)
52
- optional_attributes.each do |key|
64
+ (required_attributes + optional_attributes).each do |key|
53
65
  self.send "#{key}=", hash[key]
54
66
  end
55
- self.userinfo_endpoint ||= hash[:user_info_endpoint]
56
- self.userinfo_signing_alg_values_supported ||= hash[:user_info_signing_alg_values_supported]
57
- self.userinfo_encryption_alg_values_supported ||= hash[:user_info_encryption_alg_values_supported]
58
- self.userinfo_encryption_enc_values_supported ||= hash[:user_info_encryption_enc_values_supported]
59
- self.version ||= '3.0'
60
67
  @raw = hash
61
68
  end
62
69
 
63
70
  def as_json(options = {})
64
- hash = optional_attributes.inject({}) do |hash, _attr_|
65
- hash.merge(
66
- _attr_ => self.send(_attr_)
67
- )
68
- end
69
- hash.delete_if do |key, value|
70
- value.nil?
71
+ validate!
72
+ (required_attributes + optional_attributes).inject({}) do |hash, _attr_|
73
+ value = self.send _attr_
74
+ hash.merge! _attr_ => value unless value.nil?
75
+ hash
71
76
  end
72
77
  end
73
78
 
74
- def signing_key
75
- x509_public_key || jwk_public_key
79
+ def validate!
80
+ valid? or raise ValidationFailed.new(self)
76
81
  end
77
82
 
78
- def encryption_key
79
- if x509_encryption_url
80
- x509_public_key :for_encryption
81
- elsif jwk_encryption_url
82
- jwk_public_key :for_encryption
83
- else
84
- signing_key
85
- end
83
+ def jwks
84
+ @jwks ||= OpenIDConnect.http_client.get(jwks_uri).body.with_indifferent_access
85
+ JSON::JWK::Set.new @jwks[:keys]
86
86
  end
87
87
 
88
- private
88
+ def jwk(kid)
89
+ @jwks ||= {}
90
+ @jwks[kid] ||= JSON::JWK::Set::Fetcher.fetch(jwks_uri, kid: kid)
91
+ end
89
92
 
90
- def x509_public_key(for_encryption = false)
91
- endpoint = if for_encryption
92
- x509_encryption_url || x509_url
93
- else
94
- x509_url
95
- end
96
- if endpoint
97
- cert = OpenSSL::X509::Certificate.new OpenIDConnect.http_client.get_content(endpoint)
98
- cert.public_key
99
- end
93
+ def public_keys
94
+ @public_keys ||= jwks.collect(&:to_key)
100
95
  end
101
96
 
102
- def jwk_public_key(for_encryption = false)
103
- endpoint = if for_encryption
104
- jwk_encryption_url || jwk_url
105
- else
106
- jwk_url
107
- end
108
- if endpoint
109
- jwk_set = JSON.parse OpenIDConnect.http_client.get_content(endpoint), symbolize_names: true
110
- JSON::JWK.decode jwk_set[:keys].first
97
+ private
98
+
99
+ def validate_issuer_matching
100
+ if expected_issuer.present? && issuer != expected_issuer
101
+ if OpenIDConnect.validate_discovery_issuer
102
+ errors.add :issuer, 'mismatch'
103
+ else
104
+ OpenIDConnect.logger.warn 'ignoring issuer mismach.'
105
+ end
111
106
  end
112
107
  end
113
108
  end
114
109
  end
115
110
  end
116
111
  end
117
- end
112
+ end
@@ -4,8 +4,11 @@ module OpenIDConnect
4
4
  class Config
5
5
  def self.discover!(identifier, cache_options = {})
6
6
  uri = URI.parse(identifier)
7
- Resource.new(uri).discover!(cache_options)
8
- rescue SWD::Exception => e
7
+ Resource.new(uri).discover!(cache_options).tap do |response|
8
+ response.expected_issuer = identifier
9
+ response.validate!
10
+ end
11
+ rescue SWD::Exception, ValidationFailed => e
9
12
  raise DiscoveryFailed.new(e.message)
10
13
  end
11
14
  end
@@ -2,6 +2,8 @@ module OpenIDConnect
2
2
  module Discovery
3
3
  module Provider
4
4
  module Issuer
5
+ REL_VALUE = 'http://openid.net/specs/connect/1.0/issuer'
6
+
5
7
  def issuer
6
8
  self.link_for(REL_VALUE)[:href]
7
9
  end
@@ -9,14 +11,16 @@ module OpenIDConnect
9
11
 
10
12
  def self.discover!(identifier)
11
13
  resource = case identifier
12
- when /^acct:/, /@/, /^https?:\/\//
14
+ when /^acct:/, /https?:\/\//
13
15
  identifier
16
+ when /@/
17
+ "acct:#{identifier}"
14
18
  else
15
19
  "https://#{identifier}"
16
20
  end
17
21
  response = WebFinger.discover!(
18
22
  resource,
19
- rel: REL_VALUE
23
+ rel: Issuer::REL_VALUE
20
24
  )
21
25
  response.extend Issuer
22
26
  response
@@ -1,7 +1,5 @@
1
1
  module OpenIDConnect
2
2
  module Discovery
3
- REL_VALUE = 'http://openid.net/specs/connect/1.0/issuer'
4
-
5
3
  class InvalidIdentifier < Exception; end
6
4
  class DiscoveryFailed < Exception; end
7
5
  end
@@ -1,12 +1,14 @@
1
1
  module OpenIDConnect
2
2
  module JWTnizable
3
3
  def to_jwt(key, algorithm = :RS256, &block)
4
+ as_jwt(key, algorithm, &block).to_s
5
+ end
6
+
7
+ def as_jwt(key, algorithm = :RS256, &block)
4
8
  token = JSON::JWT.new as_json
5
9
  yield token if block_given?
6
- if algorithm != :none
7
- token = token.sign key, algorithm
8
- end
9
- token.to_s
10
+ token = token.sign key, algorithm if algorithm != :none
11
+ token
10
12
  end
11
13
  end
12
14
  end
@@ -3,12 +3,10 @@ module OpenIDConnect
3
3
  module Claimable
4
4
  def self.included(klass)
5
5
  klass.send :attr_optional, :claims
6
- klass.send :alias_method_chain, :initialize, :claims
7
- klass.send :alias_method_chain, :as_json, :keep_blank
8
6
  end
9
7
 
10
- def initialize_with_claims(attributes = {})
11
- initialize_without_claims attributes
8
+ def initialize(attributes = {})
9
+ super
12
10
  if claims.present?
13
11
  _claims_ = {}
14
12
  claims.each do |key, value|
@@ -29,9 +27,9 @@ module OpenIDConnect
29
27
  end
30
28
  end
31
29
 
32
- def as_json_with_keep_blank(options = {})
30
+ def as_json(options = {})
33
31
  keys = claims.try(:keys)
34
- hash = as_json_without_keep_blank options
32
+ hash = super
35
33
  Array(keys).each do |key|
36
34
  hash[:claims][key] ||= nil
37
35
  end
@@ -3,28 +3,21 @@ module OpenIDConnect
3
3
  include JWTnizable
4
4
 
5
5
  attr_optional :client_id, :response_type, :redirect_uri, :scope, :state, :nonce, :display, :prompt, :userinfo, :id_token
6
- alias_method :user_info, :userinfo
7
6
  validate :require_at_least_one_attributes
8
7
 
9
- def initialize(attributes = {})
10
- attributes[:userinfo] ||= attributes[:user_info]
11
- super attributes
12
- end
13
-
8
+ undef :id_token=
14
9
  def id_token=(attributes = {})
15
10
  @id_token = IdToken.new(attributes) if attributes.present?
16
11
  end
17
12
 
13
+ undef :userinfo=
18
14
  def userinfo=(attributes = {})
19
15
  @userinfo = UserInfo.new(attributes) if attributes.present?
20
16
  end
21
- alias_method :user_info=, :userinfo=
22
17
 
23
- def as_json_with_mixed_keys(options = {})
24
- hash = as_json_without_mixed_keys options
25
- hash.with_indifferent_access
18
+ def as_json(options = {})
19
+ super.with_indifferent_access
26
20
  end
27
- alias_method_chain :as_json, :mixed_keys
28
21
 
29
22
  class << self
30
23
  def decode(jwt_string, key = nil)
@@ -32,7 +25,7 @@ module OpenIDConnect
32
25
  end
33
26
 
34
27
  def fetch(request_uri, key = nil)
35
- jwt_string = OpenIDConnect.http_client.get_content(request_uri)
28
+ jwt_string = OpenIDConnect.http_client.get(request_uri).body
36
29
  decode jwt_string, key
37
30
  end
38
31
  end
@@ -41,4 +34,4 @@ end
41
34
 
42
35
  require 'openid_connect/request_object/claimable'
43
36
  require 'openid_connect/request_object/id_token'
44
- require 'openid_connect/request_object/user_info'
37
+ require 'openid_connect/request_object/user_info'
@@ -1,33 +1,41 @@
1
- require 'json/jwt'
2
-
3
1
  module OpenIDConnect
4
2
  class ResponseObject
5
3
  class IdToken < ConnectObject
6
4
  class InvalidToken < Exception; end
5
+ class ExpiredToken < InvalidToken; end
6
+ class InvalidIssuer < InvalidToken; end
7
+ class InvalidNonce < InvalidToken; end
8
+ class InvalidAudience < InvalidToken; end
7
9
 
8
10
  attr_required :iss, :sub, :aud, :exp, :iat
9
- attr_optional :acr, :auth_time, :nonce, :sub_jwk, :at_hash, :c_hash
10
- attr_accessor :access_token, :code
11
+ attr_optional :acr, :amr, :azp, :jti, :sid, :auth_time, :nonce, :sub_jwk, :at_hash, :c_hash, :s_hash
12
+ attr_accessor :access_token, :code, :state
11
13
  alias_method :subject, :sub
12
14
  alias_method :subject=, :sub=
13
15
 
14
16
  def initialize(attributes = {})
15
17
  super
16
- (all_attributes - [:exp, :iat, :auth_time, :sub_jwk]).each do |key|
18
+ (all_attributes - [:aud, :exp, :iat, :auth_time, :sub_jwk]).each do |key|
17
19
  self.send "#{key}=", self.send(key).try(:to_s)
18
20
  end
21
+ self.auth_time = auth_time.to_i unless auth_time.nil?
19
22
  end
20
23
 
21
24
  def verify!(expected = {})
22
- exp.to_i > Time.now.to_i &&
23
- iss == expected[:issuer] &&
24
- aud == expected[:client_id] &&
25
- nonce == expected[:nonce] or
26
- raise InvalidToken.new('Invalid ID Token')
25
+ raise ExpiredToken.new('Invalid ID token: Expired token') unless exp.to_i > Time.now.to_i
26
+ raise InvalidIssuer.new('Invalid ID token: Issuer does not match') unless iss == expected[:issuer]
27
+ raise InvalidNonce.new('Invalid ID Token: Nonce does not match') unless nonce == expected[:nonce]
28
+
29
+ # aud(ience) can be a string or an array of strings
30
+ unless Array(aud).include?(expected[:audience] || expected[:client_id])
31
+ raise InvalidAudience.new('Invalid ID token: Audience does not match')
32
+ end
33
+
34
+ true
27
35
  end
28
36
 
29
37
  include JWTnizable
30
- def to_jwt_with_at_hash_and_c_hash(key, algorithm = :RS256, &block)
38
+ def to_jwt(key, algorithm = :RS256, &block)
31
39
  hash_length = algorithm.to_s[2, 3].to_i
32
40
  if access_token
33
41
  token = case access_token
@@ -41,33 +49,39 @@ module OpenIDConnect
41
49
  if code
42
50
  self.c_hash = left_half_hash_of code, hash_length
43
51
  end
44
- to_jwt_without_at_hash_and_c_hash key, algorithm, &block
52
+ if state
53
+ self.s_hash = left_half_hash_of state, hash_length
54
+ end
55
+ super
45
56
  end
46
- alias_method_chain :to_jwt, :at_hash_and_c_hash
47
57
 
48
58
  private
49
59
 
50
60
  def left_half_hash_of(string, hash_length)
51
- digest = OpenSSL::Digest::Digest.new("SHA#{hash_length}").digest string
52
- UrlSafeBase64.encode64 digest[0, hash_length / (2 * 8)]
61
+ digest = OpenSSL::Digest.new("SHA#{hash_length}").digest string
62
+ Base64.urlsafe_encode64 digest[0, hash_length / (2 * 8)], padding: false
53
63
  end
54
64
 
55
65
  class << self
56
- def decode(jwt_string, key)
57
- if key == :self_issued
66
+ def decode(jwt_string, key_or_config)
67
+ case key_or_config
68
+ when :self_issued
58
69
  decode_self_issued jwt_string
70
+ when OpenIDConnect::Discovery::Provider::Config::Response
71
+ jwt = JSON::JWT.decode jwt_string, :skip_verification
72
+ jwt.verify! key_or_config.jwk(jwt.kid)
73
+ new jwt
59
74
  else
60
- new JSON::JWT.decode jwt_string, key
75
+ new JSON::JWT.decode jwt_string, key_or_config
61
76
  end
62
77
  end
63
78
 
64
79
  def decode_self_issued(jwt_string)
65
80
  jwt = JSON::JWT.decode jwt_string, :skip_verification
66
- jwk = jwt[:sub_jwk]
81
+ jwk = JSON::JWK.new jwt[:sub_jwk]
67
82
  raise InvalidToken.new('Missing sub_jwk') if jwk.blank?
68
- raise InvalidToken.new('Invalid subject') unless jwt[:sub] == self_issued_subject(jwk)
69
- public_key = JSON::JWK.decode jwk
70
- jwt = JSON::JWT.decode jwt_string, public_key
83
+ raise InvalidToken.new('Invalid subject') unless jwt[:sub] == jwk.thumbprint
84
+ jwt.verify! jwk
71
85
  new jwt
72
86
  end
73
87
 
@@ -75,24 +89,11 @@ module OpenIDConnect
75
89
  attributes[:sub_jwk] ||= JSON::JWK.new attributes.delete(:public_key)
76
90
  _attributes_ = {
77
91
  iss: 'https://self-issued.me',
78
- sub: self_issued_subject(attributes[:sub_jwk])
92
+ sub: JSON::JWK.new(attributes[:sub_jwk]).thumbprint
79
93
  }.merge(attributes)
80
94
  new _attributes_
81
95
  end
82
-
83
- def self_issued_subject(jwk)
84
- subject_base_string = case jwk[:alg].to_s
85
- when 'RSA'
86
- [jwk[:n], jwk[:e]].join
87
- when 'EC'
88
- raise NotImplementedError.new('Not Implemented Yet')
89
- else
90
- # Shouldn't reach here. All unknown algorithm error should occurs when decoding JWK
91
- raise InvalidToken.new('Unknown Algorithm')
92
- end
93
- UrlSafeBase64.encode64 OpenSSL::Digest::SHA256.digest(subject_base_string)
94
- end
95
96
  end
96
97
  end
97
98
  end
98
- end
99
+ end
@@ -0,0 +1,10 @@
1
+ module OpenIDConnect
2
+ class ResponseObject
3
+ class UserInfo
4
+ class Address < ConnectObject
5
+ attr_optional :formatted, :street_address, :locality, :region, :postal_code, :country
6
+ validate :require_at_least_one_attributes
7
+ end
8
+ end
9
+ end
10
+ end
@@ -1,3 +1,65 @@
1
- Dir[File.dirname(__FILE__) + '/user_info/*.rb'].each do |file|
1
+ module OpenIDConnect
2
+ class ResponseObject
3
+ class UserInfo < ConnectObject
4
+ attr_optional(
5
+ :sub,
6
+ :name,
7
+ :given_name,
8
+ :family_name,
9
+ :middle_name,
10
+ :nickname,
11
+ :preferred_username,
12
+ :profile,
13
+ :picture,
14
+ :website,
15
+ :email,
16
+ :email_verified,
17
+ :gender,
18
+ :birthdate,
19
+ :zoneinfo,
20
+ :locale,
21
+ :phone_number,
22
+ :phone_number_verified,
23
+ :address,
24
+ :updated_at
25
+ )
26
+ alias_method :subject, :sub
27
+ alias_method :subject=, :sub=
28
+
29
+ validates :email_verified, :phone_number_verified, allow_nil: true, inclusion: {in: [true, false]}
30
+ validates :zoneinfo, allow_nil: true, inclusion: {in: TZInfo::TimezoneProxy.all.collect(&:name)}
31
+ validates :profile, :picture, :website, allow_nil: true, url: true
32
+ validates :email, allow_nil: true, email: true
33
+ validates :updated_at, allow_nil: true, numericality: {only_integer: true}
34
+ validate :validate_address
35
+ validate :require_at_least_one_attributes
36
+ # TODO: validate locale
37
+
38
+ def initialize(attributes = {})
39
+ super
40
+ (all_attributes - [:email_verified, :phone_number_verified, :address, :updated_at]).each do |key|
41
+ self.send "#{key}=", self.send(key).try(:to_s)
42
+ end
43
+ self.updated_at = updated_at.try(:to_i)
44
+ end
45
+
46
+ def validate_address
47
+ errors.add :address, address.errors.full_messages.join(', ') if address.present? && !address.valid?
48
+ end
49
+
50
+ undef :address=
51
+ def address=(hash_or_address)
52
+ @address = case hash_or_address
53
+ when Hash
54
+ Address.new hash_or_address
55
+ when Address
56
+ hash_or_address
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ Dir[File.dirname(__FILE__) + '/user_info/*.rb'].each do |file|
2
64
  require file
3
- end
65
+ end
@@ -1,22 +1,26 @@
1
1
  require 'json'
2
2
  require 'logger'
3
+ require 'faraday'
4
+ require 'faraday/follow_redirects'
3
5
  require 'swd'
4
6
  require 'webfinger'
5
7
  require 'active_model'
6
8
  require 'tzinfo'
7
9
  require 'validate_url'
8
- require 'validate_email'
10
+ require 'email_validator/strict'
11
+ require 'mail'
9
12
  require 'attr_required'
10
13
  require 'attr_optional'
14
+ require 'json/jwt'
11
15
  require 'rack/oauth2'
16
+ require 'rack/oauth2/server/authorize/error_with_connect_ext'
12
17
  require 'rack/oauth2/server/authorize/request_with_connect_params'
13
18
  require 'rack/oauth2/server/id_token_response'
14
- require 'rack/oauth2/server/resource/error_with_connect_ext'
15
19
 
16
20
  module OpenIDConnect
17
21
  VERSION = ::File.read(
18
22
  ::File.join(::File.dirname(__FILE__), '../VERSION')
19
- )
23
+ ).chomp
20
24
 
21
25
  def self.logger
22
26
  @@logger
@@ -63,19 +67,31 @@ module OpenIDConnect
63
67
  self.debugging = false
64
68
 
65
69
  def self.http_client
66
- _http_client_ = HTTPClient.new(
67
- agent_name: "OpenIDConnect (#{VERSION})"
68
- )
69
- _http_client_.request_filter << Debugger::RequestFilter.new if debugging?
70
- http_config.try(:call, _http_client_)
71
- _http_client_
70
+ Faraday.new(headers: {user_agent: "OpenIDConnect (#{VERSION})"}) do |faraday|
71
+ faraday.request :url_encoded
72
+ faraday.request :json
73
+ faraday.response :json
74
+ faraday.adapter Faraday.default_adapter
75
+ http_config&.call(faraday)
76
+ faraday.response :logger, OpenIDConnect.logger, {bodies: true} if debugging?
77
+ end
72
78
  end
73
79
  def self.http_config(&block)
74
80
  @sub_protocols.each do |klass|
75
- klass.http_config &block unless klass.http_config
81
+ klass.http_config(&block) unless klass.http_config
76
82
  end
77
83
  @@http_config ||= block
78
84
  end
85
+
86
+ def self.validate_discovery_issuer=(boolean)
87
+ @@validate_discovery_issuer = boolean
88
+ end
89
+
90
+ def self.validate_discovery_issuer
91
+ @@validate_discovery_issuer
92
+ end
93
+
94
+ self.validate_discovery_issuer = true
79
95
  end
80
96
 
81
97
  require 'openid_connect/exception'
@@ -84,4 +100,3 @@ require 'openid_connect/access_token'
84
100
  require 'openid_connect/jwtnizable'
85
101
  require 'openid_connect/connect_object'
86
102
  require 'openid_connect/discovery'
87
- require 'openid_connect/debugger'
@@ -10,7 +10,7 @@ module Rack
10
10
  end
11
11
  end
12
12
 
13
- def call(env)
13
+ def _call(env)
14
14
  @request = Request.new env
15
15
  @response = Response.new request
16
16
  super
@@ -22,6 +22,10 @@ module Rack
22
22
  @response_type = [:code, :id_token]
23
23
  attr_missing!
24
24
  end
25
+
26
+ def error_params_location
27
+ :fragment
28
+ end
25
29
  end
26
30
 
27
31
  class Response < Authorize::Code::Response
@@ -10,7 +10,7 @@ module Rack
10
10
  end
11
11
  end
12
12
 
13
- def call(env)
13
+ def _call(env)
14
14
  @request = Request.new env
15
15
  @response = Response.new request
16
16
  super
@@ -10,7 +10,7 @@ module Rack
10
10
  end
11
11
  end
12
12
 
13
- def call(env)
13
+ def _call(env)
14
14
  @request = Request.new env
15
15
  @response = Response.new request
16
16
  super
@@ -10,7 +10,7 @@ module Rack
10
10
  end
11
11
  end
12
12
 
13
- def call(env)
13
+ def _call(env)
14
14
  @request = Request.new env
15
15
  @response = Response.new request
16
16
  super