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.
- checksums.yaml +5 -5
- data/.github/FUNDING.yml +3 -0
- data/.github/workflows/spec.yml +31 -0
- data/.gitignore +1 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE +3 -1
- data/README.rdoc +10 -3
- data/Rakefile +6 -6
- data/TODOs +12 -0
- data/VERSION +1 -1
- data/lib/openid_connect/access_token/mtls.rb +9 -0
- data/lib/openid_connect/access_token.rb +14 -6
- data/lib/openid_connect/client/registrar.rb +69 -130
- data/lib/openid_connect/client.rb +7 -12
- data/lib/openid_connect/discovery/provider/config/resource.rb +5 -3
- data/lib/openid_connect/discovery/provider/config/response.rb +73 -78
- data/lib/openid_connect/discovery/provider/config.rb +5 -2
- data/lib/openid_connect/discovery/provider.rb +6 -2
- data/lib/openid_connect/discovery.rb +0 -2
- data/lib/openid_connect/jwtnizable.rb +6 -4
- data/lib/openid_connect/request_object/claimable.rb +4 -6
- data/lib/openid_connect/request_object.rb +6 -13
- data/lib/openid_connect/response_object/id_token.rb +38 -37
- data/lib/openid_connect/response_object/user_info/address.rb +10 -0
- data/lib/openid_connect/response_object/user_info.rb +64 -2
- data/lib/openid_connect.rb +26 -11
- data/lib/rack/oauth2/server/authorize/extension/code_and_id_token.rb +5 -1
- data/lib/rack/oauth2/server/authorize/extension/code_and_id_token_and_token.rb +1 -1
- data/lib/rack/oauth2/server/authorize/extension/id_token.rb +1 -1
- data/lib/rack/oauth2/server/authorize/extension/id_token_and_token.rb +1 -1
- data/lib/rack/oauth2/server/authorize/request_with_connect_params.rb +17 -14
- data/lib/rack/oauth2/server/id_token_response.rb +11 -13
- data/openid_connect.gemspec +19 -13
- data/spec/helpers/crypto_spec_helper.rb +2 -2
- data/spec/helpers/webmock_helper.rb +14 -9
- data/spec/mock_response/access_token/without_token_type.json +3 -0
- data/spec/mock_response/discovery/config.json +3 -3
- data/spec/mock_response/discovery/config_with_custom_port.json +13 -0
- data/spec/mock_response/discovery/config_with_invalid_issuer.json +13 -0
- data/spec/mock_response/discovery/config_with_path.json +13 -0
- data/spec/mock_response/discovery/config_without_issuer.json +12 -0
- data/spec/mock_response/errors/unknown.json +3 -1
- data/spec/mock_response/public_keys/{jwk.json → jwks.json} +1 -1
- data/spec/mock_response/public_keys/jwks_with_private_key.json +8 -0
- data/spec/mock_response/public_keys/private_key.pem +27 -0
- data/spec/openid_connect/access_token_spec.rb +11 -20
- data/spec/openid_connect/client/registrar_spec.rb +93 -208
- data/spec/openid_connect/client_spec.rb +79 -22
- data/spec/openid_connect/connect_object_spec.rb +1 -1
- data/spec/openid_connect/discovery/provider/config/response_spec.rb +76 -284
- data/spec/openid_connect/discovery/provider/config_spec.rb +64 -27
- data/spec/openid_connect/discovery/provider_spec.rb +2 -2
- data/spec/openid_connect/request_object_spec.rb +4 -4
- data/spec/openid_connect/response_object/id_token_spec.rb +94 -52
- data/spec/openid_connect/response_object/user_info/{open_id/address_spec.rb → address_spec.rb} +3 -3
- data/spec/openid_connect/response_object/{user_info/open_id_spec.rb → user_info_spec.rb} +13 -12
- data/spec/openid_connect_spec.rb +19 -19
- data/spec/rack/oauth2/server/authorize/extension/code_and_id_token_and_token_spec.rb +11 -0
- data/spec/rack/oauth2/server/authorize/extension/code_and_id_token_spec.rb +11 -0
- data/spec/rack/oauth2/server/authorize/extension/id_token_and_token_spec.rb +11 -0
- data/spec/rack/oauth2/server/authorize/extension/id_token_spec.rb +1 -1
- data/spec/rack/oauth2/server/authorize/request_with_connect_params_spec.rb +45 -0
- data/spec/spec_helper.rb +12 -1
- metadata +155 -90
- data/.travis.yml +0 -3
- data/Gemfile.lock +0 -102
- data/lib/openid_connect/debugger/request_filter.rb +0 -28
- data/lib/openid_connect/debugger.rb +0 -3
- data/lib/openid_connect/response_object/user_info/open_id/address.rb +0 -12
- data/lib/openid_connect/response_object/user_info/open_id.rb +0 -64
- data/lib/rack/oauth2/server/resource/error_with_connect_ext.rb +0 -14
- data/spec/mock_response/public_keys/x509.pem +0 -21
- data/spec/openid_connect/debugger/request_filter_spec.rb +0 -33
- data/spec/rack/oauth2/server/resource/error_with_connect_ext_spec.rb +0 -12
- /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
|
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
|
-
|
10
|
-
|
11
|
-
:
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
:
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
:
|
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
|
-
:
|
42
|
-
:
|
43
|
-
:
|
44
|
-
:
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
75
|
-
|
79
|
+
def validate!
|
80
|
+
valid? or raise ValidationFailed.new(self)
|
76
81
|
end
|
77
82
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
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
|
-
|
88
|
+
def jwk(kid)
|
89
|
+
@jwks ||= {}
|
90
|
+
@jwks[kid] ||= JSON::JWK::Set::Fetcher.fetch(jwks_uri, kid: kid)
|
91
|
+
end
|
89
92
|
|
90
|
-
def
|
91
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
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:/,
|
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,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
|
-
|
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
|
11
|
-
|
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
|
30
|
+
def as_json(options = {})
|
33
31
|
keys = claims.try(:keys)
|
34
|
-
hash =
|
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
|
-
|
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
|
24
|
-
|
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.
|
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
|
-
|
25
|
-
|
26
|
-
|
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
|
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
|
-
|
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
|
52
|
-
|
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,
|
57
|
-
|
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,
|
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] ==
|
69
|
-
|
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:
|
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
|
@@ -1,3 +1,65 @@
|
|
1
|
-
|
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
|
data/lib/openid_connect.rb
CHANGED
@@ -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 '
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
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
|
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
|