openid_connect 0.6.1 → 0.7.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/README.rdoc +3 -1
  4. data/VERSION +1 -1
  5. data/lib/openid_connect/access_token.rb +1 -2
  6. data/lib/openid_connect/client.rb +2 -6
  7. data/lib/openid_connect/client/registrar.rb +59 -123
  8. data/lib/openid_connect/discovery.rb +0 -2
  9. data/lib/openid_connect/discovery/provider.rb +3 -1
  10. data/lib/openid_connect/discovery/provider/config/response.rb +57 -78
  11. data/lib/openid_connect/request_object.rb +1 -8
  12. data/lib/openid_connect/request_object/{user_info.rb → userinfo.rb} +0 -0
  13. data/lib/openid_connect/response_object/id_token.rb +1 -1
  14. data/lib/openid_connect/response_object/userinfo.rb +3 -0
  15. data/lib/openid_connect/response_object/{user_info → userinfo}/open_id.rb +7 -6
  16. data/lib/openid_connect/response_object/{user_info → userinfo}/open_id/address.rb +0 -0
  17. data/openid_connect.gemspec +2 -2
  18. data/spec/helpers/webmock_helper.rb +2 -1
  19. data/spec/mock_response/discovery/config.json +3 -2
  20. data/spec/mock_response/public_keys/{jwk.json → jwks.json} +1 -1
  21. data/spec/mock_response/{user_info → userinfo}/openid.json +0 -0
  22. data/spec/openid_connect/access_token_spec.rb +7 -6
  23. data/spec/openid_connect/client/registrar_spec.rb +82 -207
  24. data/spec/openid_connect/client_spec.rb +2 -2
  25. data/spec/openid_connect/discovery/provider/config/response_spec.rb +53 -286
  26. data/spec/openid_connect/discovery/provider/config_spec.rb +11 -12
  27. data/spec/openid_connect/discovery/provider_spec.rb +1 -1
  28. data/spec/openid_connect/request_object_spec.rb +4 -4
  29. data/spec/openid_connect/response_object/id_token_spec.rb +4 -4
  30. data/spec/openid_connect/response_object/user_info/open_id_spec.rb +1 -0
  31. metadata +17 -20
  32. data/Gemfile.lock +0 -102
  33. data/lib/openid_connect/response_object/user_info.rb +0 -3
  34. data/spec/mock_response/public_keys/x509.pem +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 806c31b986d5df085e19f74d6b7140f3db2b70c5
4
- data.tar.gz: ad199229fe09fc363fd985c047c038c874c1ef8f
3
+ metadata.gz: 7e2f19e123a06f31e0d6081e5ec4f8b05eaa56b7
4
+ data.tar.gz: 4a90cb5da6acf0bc5382562a047badd6dec4efd5
5
5
  SHA512:
6
- metadata.gz: 1a2abe2d41fc859cf818416f3f91a55f0c87644bf0254ddfa8438d905d8ac14f21abb6bdadd52744d3567c99680d95acc4e746bedb9dd48d634c3b3a7a7b70e1
7
- data.tar.gz: 25e21e4e866c87d4a3732a62152f3ecff3c0f8c24b9ea992417d6c138039c464a44c90e8c68cfec46a131878615356de8f4939a1f6aa574a71c6394e31af7b79
6
+ metadata.gz: 061da991172819aca5f12d331a8dc05116821b3bf4b56843f5c9a0cf987e3369756bad429fe135ee49322f126154d65d8f28c3b9adabac8c5acbf0380cf6264f
7
+ data.tar.gz: 814e741ecf52b5669278a88412f16b4ff2141029709bdf151f1a601b371650a7090df6de3f5eff1883b93e8d909237cc98fc4d8d7562ffadb8b50610184017e3
data/.travis.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  rvm:
2
- - 1.9.2
3
2
  - 1.9.3
3
+ - 2.0.0
data/README.rdoc CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  OpenID Connect Server & Client Library
4
4
 
5
+ {<img src="https://secure.travis-ci.org/nov/openid_connect.png" />}[http://travis-ci.org/nov/openid_connect]
6
+
5
7
  == Installation
6
8
 
7
9
  gem install openid_connect
@@ -25,7 +27,7 @@ OpenID Connect Server & Client Library
25
27
  * Source on GitHub (https://github.com/nov/openid_connect_sample_rp)
26
28
 
27
29
  == Note on Patches/Pull Requests
28
-
30
+
29
31
  * Fork the project.
30
32
  * Make your feature addition or bug fix.
31
33
  * Add tests for it. This is important so I don't break it in a
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.1
1
+ 0.7.0
@@ -14,7 +14,6 @@ module OpenIDConnect
14
14
  end
15
15
  ResponseObject::UserInfo::OpenID.new hash
16
16
  end
17
- alias_method :user_info!, :userinfo!
18
17
 
19
18
  private
20
19
 
@@ -22,7 +21,7 @@ module OpenIDConnect
22
21
  res = yield
23
22
  case res.status
24
23
  when 200
25
- JSON.parse res.body, symbolize_names: true
24
+ JSON.parse(res.body).with_indifferent_access
26
25
  when 400
27
26
  raise BadRequest.new('API Access Faild', res)
28
27
  when 401
@@ -1,13 +1,10 @@
1
1
  module OpenIDConnect
2
2
  class Client < Rack::OAuth2::Client
3
3
  attr_optional :userinfo_endpoint, :expires_in
4
- alias_method :user_info_endpoint, :userinfo_endpoint
5
- alias_method :user_info_endpoint=, :userinfo_endpoint=
6
4
 
7
5
  def initialize(attributes = {})
8
- attributes[:userinfo_endpoint] ||= attributes[:user_info_endpoint]
9
6
  super attributes
10
- self.userinfo_endpoint ||= '/user_info'
7
+ self.userinfo_endpoint ||= '/userinfo'
11
8
  end
12
9
 
13
10
  def authorization_uri(params = {})
@@ -19,7 +16,6 @@ module OpenIDConnect
19
16
  def userinfo_uri
20
17
  absolute_uri_for userinfo_endpoint
21
18
  end
22
- alias_method :user_info_uri, :userinfo_uri
23
19
 
24
20
  private
25
21
 
@@ -30,7 +26,7 @@ module OpenIDConnect
30
26
  end
31
27
 
32
28
  def handle_success_response(response)
33
- token_hash = JSON.parse response.body, symbolize_names: true
29
+ token_hash = JSON.parse(response.body).with_indifferent_access
34
30
  case token_type = token_hash[:token_type].try(:downcase)
35
31
  when 'bearer'
36
32
  AccessToken.new token_hash.merge(client: self)
@@ -5,22 +5,20 @@ module OpenIDConnect
5
5
 
6
6
  class RegistrationFailed < HttpError; end
7
7
 
8
+ cattr_accessor :plurar_uri_attributes, :metadata_attributes
9
+ singular_uri_attributes = [
10
+ :logo_uri,
11
+ :client_uri,
12
+ :policy_uri,
13
+ :tos_uri,
14
+ :jwks_uri,
15
+ :sector_identifier_uri,
16
+ :initiate_login_uri
17
+ ]
8
18
  singular_attributes = [
9
- :operation,
10
- :client_id,
11
- :client_secret,
12
- :access_token,
13
19
  :application_type,
14
20
  :client_name,
15
- :logo_url,
16
21
  :token_endpoint_auth_method,
17
- :policy_url,
18
- :tos_url,
19
- :jwk_url,
20
- :jwk_encryption_url,
21
- :x509_url,
22
- :x509_encryption_url,
23
- :sector_identifier_url,
24
22
  :subject_type,
25
23
  :request_object_signing_alg,
26
24
  :userinfo_signed_response_alg,
@@ -30,81 +28,47 @@ module OpenIDConnect
30
28
  :id_token_encrypted_response_alg,
31
29
  :id_token_encrypted_response_enc,
32
30
  :default_max_age,
33
- :require_auth_time,
34
- :default_acr,
35
- :initiate_login_uri,
36
- :post_logout_redirect_url
31
+ :require_auth_time
32
+ ] + singular_uri_attributes
33
+ self.plurar_uri_attributes = [
34
+ :redirect_uris,
35
+ :post_logout_redirect_uris,
36
+ :request_uris
37
37
  ]
38
38
  plurar_attributes = [
39
+ :response_types,
40
+ :grant_types,
39
41
  :contacts,
42
+ :default_acr_values,
43
+ ] + plurar_uri_attributes
44
+ self.metadata_attributes = singular_attributes + plurar_attributes
45
+ required_metadata_attributes = [
40
46
  :redirect_uris
41
47
  ]
42
48
  attr_required :endpoint
43
- attr_optional *(singular_attributes + plurar_attributes)
44
-
45
- plurar_attributes.each do |_attr_|
46
- define_method "#{_attr_}_with_split" do
47
- value = self.send("#{_attr_}_without_split")
48
- case value
49
- when String
50
- value.split(' ')
51
- else
52
- value
53
- end
54
- end
55
- alias_method_chain _attr_, :split
56
- end
57
-
58
- validates :operation, presence: true
59
- validates :client_id, presence: {if: ->(c) { ['client_update', 'rotate_secret'].include?(c.operation.to_s) }}
60
- validates :sector_identifier_url, presence: {if: :sector_identifier_required?}
61
-
62
- validates :operation, inclusion: {in: ['client_register', 'rotate_secret', 'client_update']}
63
- validates :application_type, inclusion: {in: ['native', 'web']}, allow_nil: true
64
- validates :subject_type, inclusion: {in: ['pairwise', 'public']}, allow_nil: true
65
- validates :token_endpoint_auth_method, inclusion: {
66
- in: ['client_secret_post', 'client_secret_basic', 'client_secret_jwt', 'private_key_jwt']
67
- }, allow_nil: true
68
-
69
- validates(
70
- :logo_url,
71
- :policy_url,
72
- :tos_url,
73
- :jwk_url,
74
- :jwk_encryption_url,
75
- :x509_url,
76
- :x509_encryption_url,
77
- :sector_identifier_url,
78
- :initiate_login_uri,
79
- :post_logout_redirect_url,
80
- url: true,
81
- allow_nil: true
82
- )
83
-
49
+ attr_optional :initial_access_token
50
+ attr_required *required_metadata_attributes
51
+ attr_optional *(metadata_attributes - required_metadata_attributes)
52
+
53
+ validates *required_attributes, presence: true
54
+ validates :sector_identifier_uri, presence: {if: :sector_identifier_required?}
55
+ validates *singular_uri_attributes, url: true, allow_nil: true
56
+ validate :validate_plurar_uri_attributes
84
57
  validate :validate_contacts
85
- validate :validate_redirect_uris
86
- validate :validate_key_urls
87
- validate :validate_signature_algorithms
88
- validate :validate_encription_algorithms
89
58
 
90
59
  def initialize(endpoint, attributes = {})
91
- @endpoint = endpoint
92
- optional_attributes.each do |_attr_|
93
- value = if _attr_ == :access_token
94
- attributes[_attr_]
95
- else
96
- attributes[_attr_].try(:to_s)
97
- end
98
- self.send "#{_attr_}=", value
60
+ self.endpoint = endpoint
61
+ self.initial_access_token = attributes[:initial_access_token]
62
+ self.class.metadata_attributes.each do |_attr_|
63
+ self.send "#{_attr_}=", attributes[_attr_]
99
64
  end
100
- attr_missing!
101
65
  end
102
66
 
103
67
  def sector_identifier
104
- if valid_uri?(sector_identifier_url)
105
- URI.parse(sector_identifier_url).host
68
+ if valid_uri?(sector_identifier_uri)
69
+ URI.parse(sector_identifier_uri).host
106
70
  else
107
- hosts = Array(redirect_uris).collect do |redirect_uri|
71
+ hosts = redirect_uris.collect do |redirect_uri|
108
72
  if valid_uri?(redirect_uri, nil)
109
73
  URI.parse(redirect_uri).host
110
74
  else
@@ -121,32 +85,21 @@ module OpenIDConnect
121
85
 
122
86
  def as_json(options = {})
123
87
  validate!
124
- (optional_attributes - [:access_token]).inject({}) do |hash, _attr_|
125
- value = self.send(_attr_)
126
- hash.merge! _attr_ => case value
127
- when Array
128
- value.collect(&:to_s).join(' ')
129
- else
130
- value
131
- end
132
- end.delete_if do |key, value|
133
- value.nil?
88
+ self.class.metadata_attributes.inject({}) do |hash, _attr_|
89
+ value = self.send _attr_
90
+ hash.merge! _attr_ => value unless value.nil?
91
+ hash
134
92
  end
135
93
  end
136
94
 
137
95
  def register!
138
- self.operation = 'client_register'
139
- post!
140
- end
141
-
142
- def rotate_secret!
143
- self.operation = 'rotate_secret'
144
- post!
96
+ handle_response do
97
+ http_client.post endpoint, to_json, 'Content-Type' => 'application/json'
98
+ end
145
99
  end
146
100
 
147
- def update!
148
- self.operation = 'client_update'
149
- post!
101
+ def read
102
+ # TODO: Do we want this feature even if we don't have rotate secret nor update metadata support?
150
103
  end
151
104
 
152
105
  def validate!
@@ -156,14 +109,13 @@ module OpenIDConnect
156
109
  private
157
110
 
158
111
  def sector_identifier_required?
159
- subject_type == 'pairwise' &&
112
+ subject_type.to_s == 'pairwise' &&
160
113
  sector_identifier.blank?
161
114
  end
162
115
 
163
116
  def valid_uri?(uri, schemes = ['http', 'https'])
164
117
  # NOTE: specify nil for schemes to allow any schemes
165
- URI::regexp(schemes).match(uri).present? &&
166
- URI.parse(uri).fragment.blank?
118
+ URI::regexp(schemes).match(uri).present?
167
119
  end
168
120
 
169
121
  def validate_contacts
@@ -180,42 +132,26 @@ module OpenIDConnect
180
132
  end
181
133
  end
182
134
 
183
- def validate_redirect_uris
184
- if redirect_uris
185
- include_invalid = redirect_uris.any? do |redirect_uri|
186
- !valid_uri?(redirect_uri, nil)
135
+ def validate_plurar_uri_attributes
136
+ self.class.plurar_uri_attributes.each do |_attr_|
137
+ if (uris = self.send(_attr_))
138
+ include_invalid = uris.any? do |uri|
139
+ !valid_uri?(uri, nil)
140
+ end
141
+ errors.add _attr_, 'includes invalid URL' if include_invalid
187
142
  end
188
- errors.add :redirect_uris, 'includes invalid URL' if include_invalid
189
- end
190
- end
191
-
192
- def validate_key_urls
193
- # TODO
194
- end
195
-
196
- def validate_signature_algorithms
197
- # TODO
198
- end
199
-
200
- def validate_encription_algorithms
201
- # TODO
202
- end
203
-
204
- def post!
205
- handle_response do
206
- http_client.post endpoint, as_json
207
143
  end
208
144
  end
209
145
 
210
146
  def http_client
211
- case access_token
147
+ case initial_access_token
212
148
  when nil
213
149
  OpenIDConnect.http_client
214
150
  when Rack::OAuth2::AccessToken::Bearer
215
- access_token
151
+ initial_access_token
216
152
  else
217
153
  Rack::OAuth2::AccessToken::Bearer.new(
218
- access_token: access_token
154
+ access_token: initial_access_token
219
155
  )
220
156
  end
221
157
  end
@@ -231,7 +167,7 @@ module OpenIDConnect
231
167
  end
232
168
 
233
169
  def handle_success_response(response)
234
- credentials = JSON.parse response.body, symbolize_names: true
170
+ credentials = JSON.parse(response.body).with_indifferent_access
235
171
  Client.new(
236
172
  identifier: credentials[:client_id],
237
173
  secret: credentials[:client_secret],
@@ -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
@@ -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
@@ -16,7 +18,7 @@ module OpenIDConnect
16
18
  end
17
19
  response = WebFinger.discover!(
18
20
  resource,
19
- rel: REL_VALUE
21
+ rel: Issuer::REL_VALUE
20
22
  )
21
23
  response.extend Issuer
22
24
  response
@@ -2,112 +2,91 @@ 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
+ uri_attributes = {
11
+ required: [
12
+ :issuer,
13
+ :jwks_uri
14
+ ],
15
+ optional: [
16
+ :authorization_endpoint,
17
+ :token_endpoint,
18
+ :userinfo_endpoint,
19
+ :check_session_endpoint,
20
+ :end_session_endpoint,
21
+ :registration_endpoint,
22
+ :service_documentation,
23
+ :op_policy_uri,
24
+ :op_tos_uri
25
+ ]
26
+ }
27
+ attr_required *(uri_attributes[:required] + [
24
28
  :response_types_supported,
25
- :acr_values_supported,
26
29
  :subject_types_supported,
27
- :claims_supported,
30
+ :id_token_signing_alg_values_supported
31
+ ])
32
+ attr_optional *(uri_attributes[:optional] + [
33
+ :scopes_supported,
34
+ :grant_types_supported,
35
+ :acr_values_supported,
28
36
  :userinfo_signing_alg_values_supported,
29
37
  :userinfo_encryption_alg_values_supported,
30
38
  :userinfo_encryption_enc_values_supported,
31
- :id_token_signing_alg_values_supported,
32
39
  :id_token_encryption_alg_values_supported,
33
40
  :id_token_encryption_enc_values_supported,
34
41
  :request_object_signing_alg_values_supported,
35
42
  :request_object_encryption_alg_values_supported,
36
43
  :request_object_encryption_enc_values_supported,
37
44
  :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
45
+ :token_endpoint_auth_signing_alg_values_supported,
46
+ :display_values_supported,
47
+ :claim_types_supported,
48
+ :claims_supported,
49
+ :claims_locales_supported,
50
+ :ui_locales_supported,
51
+ :claims_parameter_supported,
52
+ :request_parameter_supported,
53
+ :request_uri_parameter_supported,
54
+ :require_request_uri_registration
55
+ ])
56
+
57
+ validates *required_attributes, presence: true
58
+ validates *uri_attributes.values.flatten, url: true, allow_nil: true
50
59
 
51
60
  def initialize(hash)
52
- optional_attributes.each do |key|
61
+ (required_attributes + optional_attributes).each do |key|
53
62
  self.send "#{key}=", hash[key]
54
63
  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
64
  @raw = hash
61
65
  end
62
66
 
63
67
  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?
68
+ validate!
69
+ (required_attributes + optional_attributes).inject({}) do |hash, _attr_|
70
+ value = self.send _attr_
71
+ hash.merge! _attr_ => value unless value.nil?
72
+ hash
71
73
  end
72
74
  end
73
75
 
74
- def signing_key
75
- x509_public_key || jwk_public_key
76
+ def validate!
77
+ valid? or raise ValidationFailed.new(self)
76
78
  end
77
79
 
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
80
+ def jwks
81
+ @jwks ||= JSON.parse(
82
+ OpenIDConnect.http_client.get_content(jwks_uri)
83
+ ).with_indifferent_access
84
+ JSON::JWK::Set.new @jwks[:keys]
86
85
  end
87
86
 
88
- private
89
-
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
100
- end
101
-
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
87
+ def public_keys
88
+ @public_keys ||= jwks.collect do |jwk|
89
+ JSON::JWK.decode jwk
111
90
  end
112
91
  end
113
92
  end