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.
- 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
|