openid_connect 0.6.1 → 0.7.0

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