openid_connect 0.6.1 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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