signet 0.11.0 → 0.14.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -15
- data/Gemfile +5 -4
- data/README.md +4 -6
- data/Rakefile +107 -37
- data/lib/signet.rb +17 -14
- data/lib/signet/errors.rb +4 -4
- data/lib/signet/oauth_1.rb +129 -154
- data/lib/signet/oauth_1/client.rb +309 -343
- data/lib/signet/oauth_1/credential.rb +40 -37
- data/lib/signet/oauth_1/server.rb +197 -203
- data/lib/signet/oauth_1/signature_methods/hmac_sha1.rb +11 -10
- data/lib/signet/oauth_1/signature_methods/plaintext.rb +8 -7
- data/lib/signet/oauth_1/signature_methods/rsa_sha1.rb +11 -11
- data/lib/signet/oauth_2.rb +41 -43
- data/lib/signet/oauth_2/client.rb +328 -313
- data/lib/signet/version.rb +2 -73
- data/signet.gemspec +37 -39
- data/spec/signet/oauth_1/client_spec.rb +313 -315
- data/spec/signet/oauth_1/credential_spec.rb +64 -56
- data/spec/signet/oauth_1/server_spec.rb +362 -362
- data/spec/signet/oauth_1/signature_methods/hmac_sha1_spec.rb +26 -26
- data/spec/signet/oauth_1/signature_methods/plaintext_spec.rb +28 -28
- data/spec/signet/oauth_1/signature_methods/rsa_sha1_spec.rb +34 -35
- data/spec/signet/oauth_1_spec.rb +553 -524
- data/spec/signet/oauth_2/client_spec.rb +652 -576
- data/spec/signet/oauth_2_spec.rb +88 -89
- data/spec/signet_spec.rb +41 -41
- data/spec/spec_helper.rb +7 -7
- data/spec/spec_helper_spec.rb +8 -8
- metadata +64 -52
- data/tasks/clobber.rake +0 -2
- data/tasks/gem.rake +0 -34
- data/tasks/git.rake +0 -40
- data/tasks/metrics.rake +0 -41
- data/tasks/spec.rake +0 -34
- data/tasks/wiki.rake +0 -38
- data/tasks/yard.rake +0 -21
@@ -1,23 +1,24 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "openssl"
|
2
|
+
require "signet"
|
3
3
|
|
4
4
|
module Signet #:nodoc:
|
5
5
|
module OAuth1
|
6
6
|
module HMACSHA1
|
7
|
-
def self.generate_signature
|
8
|
-
base_string, client_credential_secret, token_credential_secret
|
7
|
+
def self.generate_signature \
|
8
|
+
base_string, client_credential_secret, token_credential_secret
|
9
|
+
|
9
10
|
# Both the client secret and token secret must be escaped
|
10
11
|
client_credential_secret =
|
11
|
-
Signet::OAuth1.encode
|
12
|
+
Signet::OAuth1.encode client_credential_secret
|
12
13
|
token_credential_secret =
|
13
|
-
Signet::OAuth1.encode
|
14
|
+
Signet::OAuth1.encode token_credential_secret
|
14
15
|
# The key for the signature is just the client secret and token
|
15
16
|
# secret joined by the '&' character. If the token secret is omitted,
|
16
17
|
# the '&' must still be present.
|
17
|
-
key = [client_credential_secret, token_credential_secret].join
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
key = [client_credential_secret, token_credential_secret].join "&"
|
19
|
+
Base64.encode64(OpenSSL::HMAC.digest(
|
20
|
+
OpenSSL::Digest.new("sha1"), key, base_string
|
21
|
+
)).strip
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|
@@ -1,20 +1,21 @@
|
|
1
|
-
require
|
1
|
+
require "signet"
|
2
2
|
|
3
3
|
module Signet #:nodoc:
|
4
4
|
module OAuth1
|
5
5
|
module PLAINTEXT
|
6
|
-
def self.generate_signature
|
7
|
-
|
6
|
+
def self.generate_signature \
|
7
|
+
_base_string, client_credential_secret, token_credential_secret
|
8
|
+
|
8
9
|
# Both the client secret and token secret must be escaped
|
9
10
|
client_credential_secret =
|
10
|
-
|
11
|
+
Signet::OAuth1.encode client_credential_secret
|
11
12
|
token_credential_secret =
|
12
|
-
|
13
|
+
Signet::OAuth1.encode token_credential_secret
|
13
14
|
# The key for the signature is just the client secret and token
|
14
15
|
# secret joined by the '&' character. If the token secret is omitted,
|
15
16
|
# the '&' must still be present.
|
16
|
-
key = [client_credential_secret, token_credential_secret].join
|
17
|
-
|
17
|
+
key = [client_credential_secret, token_credential_secret].join "&"
|
18
|
+
Signet::OAuth1.encode(key).strip
|
18
19
|
end
|
19
20
|
end
|
20
21
|
end
|
@@ -1,20 +1,20 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require "digest/sha1"
|
2
|
+
require "base64"
|
3
|
+
require "openssl"
|
4
|
+
require "signet"
|
5
5
|
|
6
6
|
module Signet #:nodoc:
|
7
7
|
module OAuth1
|
8
8
|
module RSASHA1
|
9
|
-
def self.generate_signature
|
10
|
-
base_string, client_credential_secret,
|
9
|
+
def self.generate_signature \
|
10
|
+
base_string, client_credential_secret, _token_credential_secret
|
11
11
|
|
12
|
-
private_key = OpenSSL::PKey::RSA.new(client_credential_secret)
|
13
|
-
signature = private_key.sign(OpenSSL::Digest::SHA1.new, base_string)
|
14
|
-
#using strict_encode64 because the encode64 method adds newline characters after ever 60 chars
|
15
|
-
return Base64.strict_encode64(signature).strip
|
16
|
-
end
|
17
12
|
|
13
|
+
private_key = OpenSSL::PKey::RSA.new client_credential_secret
|
14
|
+
signature = private_key.sign OpenSSL::Digest::SHA1.new, base_string
|
15
|
+
# using strict_encode64 because the encode64 method adds newline characters after ever 60 chars
|
16
|
+
Base64.strict_encode64(signature).strip
|
17
|
+
end
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
data/lib/signet/oauth_2.rb
CHANGED
@@ -12,9 +12,9 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
15
|
+
require "base64"
|
16
|
+
require "signet"
|
17
|
+
require "multi_json"
|
18
18
|
|
19
19
|
module Signet #:nodoc:
|
20
20
|
##
|
@@ -23,63 +23,61 @@ module Signet #:nodoc:
|
|
23
23
|
# This module will be updated periodically to support newer drafts of the
|
24
24
|
# specification, as they become widely deployed.
|
25
25
|
module OAuth2
|
26
|
-
def self.parse_authorization_header
|
26
|
+
def self.parse_authorization_header field_value
|
27
27
|
auth_scheme = field_value[/^([-._0-9a-zA-Z]+)/, 1]
|
28
28
|
case auth_scheme
|
29
29
|
when /^Basic$/i
|
30
30
|
# HTTP Basic is allowed in OAuth 2
|
31
|
-
return
|
31
|
+
return parse_basic_credentials(field_value[/^Basic\s+(.*)$/i, 1])
|
32
32
|
when /^OAuth$/i
|
33
33
|
# Other token types may be supported eventually
|
34
|
-
return
|
34
|
+
return parse_bearer_credentials(field_value[/^OAuth\s+(.*)$/i, 1])
|
35
35
|
else
|
36
36
|
raise ParseError,
|
37
|
-
|
37
|
+
"Parsing non-OAuth Authorization headers is out of scope."
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
def self.parse_www_authenticate_header
|
41
|
+
def self.parse_www_authenticate_header field_value
|
42
42
|
auth_scheme = field_value[/^([-._0-9a-zA-Z]+)/, 1]
|
43
43
|
case auth_scheme
|
44
44
|
when /^OAuth$/i
|
45
45
|
# Other token types may be supported eventually
|
46
|
-
return
|
46
|
+
return parse_oauth_challenge(field_value[/^OAuth\s+(.*)$/i, 1])
|
47
47
|
else
|
48
48
|
raise ParseError,
|
49
|
-
|
49
|
+
"Parsing non-OAuth WWW-Authenticate headers is out of scope."
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
def self.parse_basic_credentials
|
54
|
-
decoded = Base64.decode64
|
55
|
-
client_id, client_secret = decoded.split
|
56
|
-
|
53
|
+
def self.parse_basic_credentials credential_string
|
54
|
+
decoded = Base64.decode64 credential_string
|
55
|
+
client_id, client_secret = decoded.split ":", 2
|
56
|
+
[["client_id", client_id], ["client_secret", client_secret]]
|
57
57
|
end
|
58
58
|
|
59
|
-
def self.parse_bearer_credentials
|
59
|
+
def self.parse_bearer_credentials credential_string
|
60
60
|
access_token = credential_string[/^([^,\s]+)(?:\s|,|$)/i, 1]
|
61
61
|
parameters = []
|
62
|
-
parameters << [
|
62
|
+
parameters << ["access_token", access_token]
|
63
63
|
auth_param_string = credential_string[/^(?:[^,\s]+)\s*,\s*(.*)$/i, 1]
|
64
64
|
if auth_param_string
|
65
65
|
# This code will rarely get called, but is included for completeness
|
66
|
-
parameters.concat
|
66
|
+
parameters.concat Signet.parse_auth_param_list(auth_param_string)
|
67
67
|
end
|
68
|
-
|
68
|
+
parameters
|
69
69
|
end
|
70
70
|
|
71
|
-
def self.parse_oauth_challenge
|
72
|
-
|
71
|
+
def self.parse_oauth_challenge challenge_string
|
72
|
+
Signet.parse_auth_param_list challenge_string
|
73
73
|
end
|
74
74
|
|
75
|
-
def self.parse_credentials
|
76
|
-
|
77
|
-
raise TypeError, "Expected String, got #{body.class}."
|
78
|
-
end
|
75
|
+
def self.parse_credentials body, content_type
|
76
|
+
raise TypeError, "Expected String, got #{body.class}." unless body.is_a? String
|
79
77
|
case content_type
|
80
|
-
when
|
81
|
-
return MultiJson.load
|
82
|
-
when
|
78
|
+
when %r{^application/json.*}
|
79
|
+
return MultiJson.load body
|
80
|
+
when %r{^application/x-www-form-urlencoded.*}
|
83
81
|
return Hash[Addressable::URI.form_unencode(body)]
|
84
82
|
else
|
85
83
|
raise ArgumentError, "Invalid content type '#{content_type}'"
|
@@ -97,14 +95,14 @@ module Signet #:nodoc:
|
|
97
95
|
#
|
98
96
|
# @return [String]
|
99
97
|
# The value for the HTTP Basic Authorization header.
|
100
|
-
def self.generate_basic_authorization_header
|
98
|
+
def self.generate_basic_authorization_header client_id, client_password
|
101
99
|
if client_id =~ /:/
|
102
100
|
raise ArgumentError,
|
103
|
-
|
101
|
+
"A client identifier may not contain a ':' character."
|
104
102
|
end
|
105
|
-
|
106
|
-
client_id +
|
107
|
-
).
|
103
|
+
"Basic " + Base64.encode64(
|
104
|
+
client_id + ":" + client_password
|
105
|
+
).delete("\n")
|
108
106
|
end
|
109
107
|
|
110
108
|
##
|
@@ -117,19 +115,19 @@ module Signet #:nodoc:
|
|
117
115
|
#
|
118
116
|
# @return [String]
|
119
117
|
# The value for the HTTP Basic Authorization header.
|
120
|
-
def self.generate_bearer_authorization_header
|
121
|
-
|
118
|
+
def self.generate_bearer_authorization_header \
|
119
|
+
access_token, auth_params = nil
|
120
|
+
|
122
121
|
# TODO: escaping?
|
123
122
|
header = "Bearer #{access_token}"
|
124
123
|
if auth_params && !auth_params.empty?
|
125
124
|
header += (", " +
|
126
|
-
(auth_params.
|
125
|
+
(auth_params.each_with_object [] do |(key, value), accu|
|
127
126
|
accu << "#{key}=\"#{value}\""
|
128
|
-
accu
|
129
127
|
end).join(", ")
|
130
|
-
|
128
|
+
)
|
131
129
|
end
|
132
|
-
|
130
|
+
header
|
133
131
|
end
|
134
132
|
|
135
133
|
##
|
@@ -140,15 +138,15 @@ module Signet #:nodoc:
|
|
140
138
|
# The base authorization endpoint URI.
|
141
139
|
#
|
142
140
|
# @return [String] The authorization URI to redirect the user to.
|
143
|
-
def self.generate_authorization_uri
|
144
|
-
|
145
|
-
parameters.delete
|
141
|
+
def self.generate_authorization_uri authorization_uri, parameters = {}
|
142
|
+
parameters.each do |key, value|
|
143
|
+
parameters.delete key if value.nil?
|
146
144
|
end
|
147
145
|
parsed_uri = Addressable::URI.parse(authorization_uri).dup
|
148
146
|
query_values = parsed_uri.query_values || {}
|
149
|
-
query_values = query_values.merge
|
147
|
+
query_values = query_values.merge parameters
|
150
148
|
parsed_uri.query_values = query_values
|
151
|
-
|
149
|
+
parsed_uri.normalize.to_s
|
152
150
|
end
|
153
151
|
end
|
154
152
|
end
|
@@ -12,20 +12,19 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
18
|
-
require
|
19
|
-
require
|
20
|
-
require
|
21
|
-
require
|
22
|
-
require
|
15
|
+
require "faraday"
|
16
|
+
require "stringio"
|
17
|
+
require "addressable/uri"
|
18
|
+
require "signet"
|
19
|
+
require "signet/errors"
|
20
|
+
require "signet/oauth_2"
|
21
|
+
require "jwt"
|
22
|
+
require "date"
|
23
23
|
|
24
24
|
module Signet
|
25
25
|
module OAuth2
|
26
26
|
class Client
|
27
|
-
|
28
|
-
OOB_MODES = %w(urn:ietf:wg:oauth:2.0:oob:auto urn:ietf:wg:oauth:2.0:oob oob)
|
27
|
+
OOB_MODES = ["urn:ietf:wg:oauth:2.0:oob:auto", "urn:ietf:wg:oauth:2.0:oob", "oob"].freeze
|
29
28
|
|
30
29
|
##
|
31
30
|
# Creates an OAuth 2.0 client.
|
@@ -47,6 +46,9 @@ module Signet
|
|
47
46
|
# - <code>:scope</code> -
|
48
47
|
# The scope of the access request, expressed either as an Array
|
49
48
|
# or as a space-delimited String.
|
49
|
+
# - <code>:target_audience</code> -
|
50
|
+
# The final target audience for ID tokens fetched by this client,
|
51
|
+
# as a String.
|
50
52
|
# - <code>:state</code> -
|
51
53
|
# An arbitrary string designed to allow the client to maintain state.
|
52
54
|
# - <code>:code</code> -
|
@@ -89,7 +91,7 @@ module Signet
|
|
89
91
|
# )
|
90
92
|
#
|
91
93
|
# @see Signet::OAuth2::Client#update!
|
92
|
-
def initialize options={}
|
94
|
+
def initialize options = {}
|
93
95
|
@authorization_uri = nil
|
94
96
|
@token_credential_uri = nil
|
95
97
|
@client_id = nil
|
@@ -102,11 +104,15 @@ module Signet
|
|
102
104
|
@principal = nil
|
103
105
|
@redirect_uri = nil
|
104
106
|
@scope = nil
|
107
|
+
@target_audience = nil
|
105
108
|
@state = nil
|
106
109
|
@username = nil
|
107
110
|
@access_type = nil
|
108
|
-
|
111
|
+
update! options
|
109
112
|
end
|
113
|
+
# rubocop:disable Metrics/AbcSize
|
114
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
115
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
110
116
|
|
111
117
|
##
|
112
118
|
# Updates an OAuth 2.0 client.
|
@@ -128,6 +134,9 @@ module Signet
|
|
128
134
|
# - <code>:scope</code> -
|
129
135
|
# The scope of the access request, expressed either as an Array
|
130
136
|
# or as a space-delimited String.
|
137
|
+
# - <code>:target_audience</code> -
|
138
|
+
# The final target audience for ID tokens fetched by this client,
|
139
|
+
# as a String.
|
131
140
|
# - <code>:state</code> -
|
132
141
|
# An arbitrary string designed to allow the client to maintain state.
|
133
142
|
# - <code>:code</code> -
|
@@ -170,32 +179,36 @@ module Signet
|
|
170
179
|
#
|
171
180
|
# @see Signet::OAuth2::Client#initialize
|
172
181
|
# @see Signet::OAuth2::Client#update_token!
|
173
|
-
def update!
|
182
|
+
def update! options = {}
|
174
183
|
# Normalize all keys to symbols to allow indifferent access.
|
175
|
-
options = deep_hash_normalize
|
176
|
-
|
177
|
-
self.authorization_uri = options[:authorization_uri] if options.
|
178
|
-
self.token_credential_uri = options[:token_credential_uri] if options.
|
179
|
-
self.client_id = options[:client_id] if options.
|
180
|
-
self.client_secret = options[:client_secret] if options.
|
181
|
-
self.scope = options[:scope] if options.
|
182
|
-
self.
|
183
|
-
self.
|
184
|
-
self.
|
185
|
-
self.
|
186
|
-
self.
|
187
|
-
self.
|
188
|
-
self.
|
189
|
-
self.
|
184
|
+
options = deep_hash_normalize options
|
185
|
+
|
186
|
+
self.authorization_uri = options[:authorization_uri] if options.key? :authorization_uri
|
187
|
+
self.token_credential_uri = options[:token_credential_uri] if options.key? :token_credential_uri
|
188
|
+
self.client_id = options[:client_id] if options.key? :client_id
|
189
|
+
self.client_secret = options[:client_secret] if options.key? :client_secret
|
190
|
+
self.scope = options[:scope] if options.key? :scope
|
191
|
+
self.target_audience = options[:target_audience] if options.key? :target_audience
|
192
|
+
self.state = options[:state] if options.key? :state
|
193
|
+
self.code = options[:code] if options.key? :code
|
194
|
+
self.redirect_uri = options[:redirect_uri] if options.key? :redirect_uri
|
195
|
+
self.username = options[:username] if options.key? :username
|
196
|
+
self.password = options[:password] if options.key? :password
|
197
|
+
self.issuer = options[:issuer] if options.key? :issuer
|
198
|
+
self.person = options[:person] if options.key? :person
|
199
|
+
self.sub = options[:sub] if options.key? :sub
|
190
200
|
self.expiry = options[:expiry] || 60
|
191
|
-
self.audience = options[:audience] if options.
|
192
|
-
self.signing_key = options[:signing_key] if options.
|
201
|
+
self.audience = options[:audience] if options.key? :audience
|
202
|
+
self.signing_key = options[:signing_key] if options.key? :signing_key
|
193
203
|
self.extension_parameters = options[:extension_parameters] || {}
|
194
204
|
self.additional_parameters = options[:additional_parameters] || {}
|
195
205
|
self.access_type = options.fetch(:access_type) { :offline }
|
196
|
-
|
197
|
-
|
206
|
+
update_token! options
|
207
|
+
self
|
198
208
|
end
|
209
|
+
# rubocop:enable Metrics/AbcSize
|
210
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
211
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
199
212
|
|
200
213
|
##
|
201
214
|
# Updates an OAuth 2.0 client.
|
@@ -225,29 +238,33 @@ module Signet
|
|
225
238
|
#
|
226
239
|
# @see Signet::OAuth2::Client#initialize
|
227
240
|
# @see Signet::OAuth2::Client#update!
|
228
|
-
def update_token!
|
241
|
+
def update_token! options = {}
|
229
242
|
# Normalize all keys to symbols to allow indifferent access internally
|
230
|
-
options = deep_hash_normalize
|
243
|
+
options = deep_hash_normalize options
|
231
244
|
|
232
|
-
self.expires_in = options[:expires] if options.
|
233
|
-
self.expires_in = options[:expires_in] if options.
|
234
|
-
self.expires_at = options[:expires_at] if options.
|
245
|
+
self.expires_in = options[:expires] if options.key? :expires
|
246
|
+
self.expires_in = options[:expires_in] if options.key? :expires_in
|
247
|
+
self.expires_at = options[:expires_at] if options.key? :expires_at
|
235
248
|
|
236
249
|
# By default, the token is issued at `Time.now` when `expires_in` is
|
237
250
|
# set, but this can be used to supply a more precise time.
|
238
|
-
self.issued_at = options[:issued_at] if options.
|
251
|
+
self.issued_at = options[:issued_at] if options.key? :issued_at
|
239
252
|
|
240
253
|
# Special case where we want expires_at to be relative to issued_at
|
241
|
-
if options.
|
254
|
+
if options.key?(:issued_at) && options.key?(:expires_in)
|
242
255
|
set_relative_expires_at options[:issued_at], options[:expires_in]
|
243
256
|
end
|
244
257
|
|
245
|
-
self.access_token = options[:access_token] if options.
|
246
|
-
self.refresh_token = options[:refresh_token] if options.
|
247
|
-
self.id_token = options[:id_token] if options.
|
258
|
+
self.access_token = options[:access_token] if options.key? :access_token
|
259
|
+
self.refresh_token = options[:refresh_token] if options.key? :refresh_token
|
260
|
+
self.id_token = options[:id_token] if options.key? :id_token
|
248
261
|
|
249
|
-
|
262
|
+
self
|
250
263
|
end
|
264
|
+
# rubocop:disable Metrics/AbcSize
|
265
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
266
|
+
# rubocop:disable Metrics/MethodLength
|
267
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
251
268
|
|
252
269
|
##
|
253
270
|
# Returns the authorization URI that the user should be redirected to.
|
@@ -255,34 +272,24 @@ module Signet
|
|
255
272
|
# @return [Addressable::URI] The authorization URI.
|
256
273
|
#
|
257
274
|
# @see Signet::OAuth2.generate_authorization_uri
|
258
|
-
def authorization_uri
|
275
|
+
def authorization_uri options = {}
|
259
276
|
# Normalize external input
|
260
|
-
options = deep_hash_normalize
|
277
|
+
options = deep_hash_normalize options
|
261
278
|
|
262
|
-
return nil if @authorization_uri
|
263
|
-
unless options[:response_type]
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
options[:access_type] = access_type
|
268
|
-
end
|
269
|
-
options[:client_id] ||= self.client_id
|
270
|
-
options[:redirect_uri] ||= self.redirect_uri
|
279
|
+
return nil if @authorization_uri.nil?
|
280
|
+
options[:response_type] = :code unless options[:response_type]
|
281
|
+
options[:access_type] = access_type if !options[:access_type] && access_type
|
282
|
+
options[:client_id] ||= client_id
|
283
|
+
options[:redirect_uri] ||= redirect_uri
|
271
284
|
if options[:prompt] && options[:approval_prompt]
|
272
285
|
raise ArgumentError, "prompt and approval_prompt are mutually exclusive parameters"
|
273
286
|
end
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
unless options[:
|
278
|
-
|
279
|
-
|
280
|
-
if !options[:scope] && self.scope
|
281
|
-
options[:scope] = self.scope.join(' ')
|
282
|
-
end
|
283
|
-
options[:state] = self.state unless options[:state]
|
284
|
-
options.merge!(self.additional_parameters.merge(options[:additional_parameters] || {}))
|
285
|
-
options.delete(:additional_parameters)
|
287
|
+
raise ArgumentError, "Missing required client identifier." unless options[:client_id]
|
288
|
+
raise ArgumentError, "Missing required redirect URI." unless options[:redirect_uri]
|
289
|
+
options[:scope] = scope.join " " if !options[:scope] && scope
|
290
|
+
options[:state] = state unless options[:state]
|
291
|
+
options.merge!(additional_parameters.merge(options[:additional_parameters] || {}))
|
292
|
+
options.delete :additional_parameters
|
286
293
|
options = Hash[options.map do |key, option|
|
287
294
|
[key.to_s, option]
|
288
295
|
end]
|
@@ -291,20 +298,24 @@ module Signet
|
|
291
298
|
@authorization_uri, options
|
292
299
|
)
|
293
300
|
)
|
294
|
-
if uri.normalized_scheme !=
|
301
|
+
if uri.normalized_scheme != "https"
|
295
302
|
raise Signet::UnsafeOperationError,
|
296
|
-
|
303
|
+
"Authorization endpoint must be protected by TLS."
|
297
304
|
end
|
298
|
-
|
305
|
+
uri
|
299
306
|
end
|
307
|
+
# rubocop:enable Metrics/AbcSize
|
308
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
309
|
+
# rubocop:enable Metrics/MethodLength
|
310
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
300
311
|
|
301
312
|
##
|
302
313
|
# Sets the authorization URI for this client.
|
303
314
|
#
|
304
315
|
# @param [Addressable::URI, Hash, String, #to_str] new_authorization_uri
|
305
316
|
# The authorization URI.
|
306
|
-
def authorization_uri=
|
307
|
-
@authorization_uri = coerce_uri
|
317
|
+
def authorization_uri= new_authorization_uri
|
318
|
+
@authorization_uri = coerce_uri new_authorization_uri
|
308
319
|
end
|
309
320
|
|
310
321
|
##
|
@@ -312,7 +323,7 @@ module Signet
|
|
312
323
|
#
|
313
324
|
# @return [Addressable::URI] The token credential URI.
|
314
325
|
def token_credential_uri
|
315
|
-
|
326
|
+
@token_credential_uri
|
316
327
|
end
|
317
328
|
|
318
329
|
##
|
@@ -320,17 +331,17 @@ module Signet
|
|
320
331
|
#
|
321
332
|
# @param [Addressable::URI, Hash, String, #to_str] new_token_credential_uri
|
322
333
|
# The token credential URI.
|
323
|
-
def token_credential_uri=
|
324
|
-
@token_credential_uri = coerce_uri
|
334
|
+
def token_credential_uri= new_token_credential_uri
|
335
|
+
@token_credential_uri = coerce_uri new_token_credential_uri
|
325
336
|
end
|
326
337
|
|
327
338
|
# Addressable expects URIs formatted as hashes to come in with symbols as keys.
|
328
339
|
# Returns nil implicitly for the nil case.
|
329
|
-
def coerce_uri
|
340
|
+
def coerce_uri incoming_uri
|
330
341
|
if incoming_uri.is_a? Hash
|
331
|
-
Addressable::URI.new
|
342
|
+
Addressable::URI.new deep_hash_normalize(incoming_uri)
|
332
343
|
elsif incoming_uri
|
333
|
-
Addressable::URI.parse
|
344
|
+
Addressable::URI.parse incoming_uri
|
334
345
|
end
|
335
346
|
end
|
336
347
|
|
@@ -339,7 +350,7 @@ module Signet
|
|
339
350
|
#
|
340
351
|
# @return [String, Symbol] The current access type.
|
341
352
|
def access_type
|
342
|
-
|
353
|
+
@access_type
|
343
354
|
end
|
344
355
|
|
345
356
|
##
|
@@ -347,7 +358,7 @@ module Signet
|
|
347
358
|
#
|
348
359
|
# @param [String, Symbol] new_access_type
|
349
360
|
# The current access type.
|
350
|
-
def access_type=
|
361
|
+
def access_type= new_access_type
|
351
362
|
@access_type = new_access_type
|
352
363
|
end
|
353
364
|
|
@@ -356,7 +367,7 @@ module Signet
|
|
356
367
|
#
|
357
368
|
# @return [String] The client identifier.
|
358
369
|
def client_id
|
359
|
-
|
370
|
+
@client_id
|
360
371
|
end
|
361
372
|
|
362
373
|
##
|
@@ -364,7 +375,7 @@ module Signet
|
|
364
375
|
#
|
365
376
|
# @param [String] new_client_id
|
366
377
|
# The client identifier.
|
367
|
-
def client_id=
|
378
|
+
def client_id= new_client_id
|
368
379
|
@client_id = new_client_id
|
369
380
|
end
|
370
381
|
|
@@ -373,7 +384,7 @@ module Signet
|
|
373
384
|
#
|
374
385
|
# @return [String] The client secret.
|
375
386
|
def client_secret
|
376
|
-
|
387
|
+
@client_secret
|
377
388
|
end
|
378
389
|
|
379
390
|
##
|
@@ -381,7 +392,7 @@ module Signet
|
|
381
392
|
#
|
382
393
|
# @param [String] new_client_secret
|
383
394
|
# The client secret.
|
384
|
-
def client_secret=
|
395
|
+
def client_secret= new_client_secret
|
385
396
|
@client_secret = new_client_secret
|
386
397
|
end
|
387
398
|
|
@@ -391,7 +402,7 @@ module Signet
|
|
391
402
|
#
|
392
403
|
# @return [Array] The scope of access the client is requesting.
|
393
404
|
def scope
|
394
|
-
|
405
|
+
@scope
|
395
406
|
end
|
396
407
|
|
397
408
|
##
|
@@ -401,18 +412,18 @@ module Signet
|
|
401
412
|
# The scope of access the client is requesting. This may be
|
402
413
|
# expressed as either an Array of String objects or as a
|
403
414
|
# space-delimited String.
|
404
|
-
def scope=
|
415
|
+
def scope= new_scope
|
405
416
|
case new_scope
|
406
417
|
when Array
|
407
418
|
new_scope.each do |scope|
|
408
|
-
if scope.include?
|
419
|
+
if scope.include? " "
|
409
420
|
raise ArgumentError,
|
410
|
-
|
421
|
+
"Individual scopes cannot contain the space character."
|
411
422
|
end
|
412
423
|
end
|
413
424
|
@scope = new_scope
|
414
425
|
when String
|
415
|
-
@scope = new_scope.split
|
426
|
+
@scope = new_scope.split " "
|
416
427
|
when nil
|
417
428
|
@scope = nil
|
418
429
|
else
|
@@ -420,12 +431,28 @@ module Signet
|
|
420
431
|
end
|
421
432
|
end
|
422
433
|
|
434
|
+
##
|
435
|
+
# Returns the final target audience for ID tokens fetched by this client.
|
436
|
+
#
|
437
|
+
# @return [String] The target audience.
|
438
|
+
def target_audience
|
439
|
+
@target_audience
|
440
|
+
end
|
441
|
+
|
442
|
+
##
|
443
|
+
# Sets the final target audience for ID tokens fetched by this client.
|
444
|
+
#
|
445
|
+
# @param [String] new_target_audience The new target audience.
|
446
|
+
def target_audience= new_target_audience
|
447
|
+
@target_audience = new_target_audience
|
448
|
+
end
|
449
|
+
|
423
450
|
##
|
424
451
|
# Returns the client's current state value.
|
425
452
|
#
|
426
453
|
# @return [String] The state value.
|
427
454
|
def state
|
428
|
-
|
455
|
+
@state
|
429
456
|
end
|
430
457
|
|
431
458
|
##
|
@@ -433,7 +460,7 @@ module Signet
|
|
433
460
|
#
|
434
461
|
# @param [String] new_state
|
435
462
|
# The state value.
|
436
|
-
def state=
|
463
|
+
def state= new_state
|
437
464
|
@state = new_state
|
438
465
|
end
|
439
466
|
|
@@ -443,7 +470,7 @@ module Signet
|
|
443
470
|
#
|
444
471
|
# @return [String] The authorization code.
|
445
472
|
def code
|
446
|
-
|
473
|
+
@code
|
447
474
|
end
|
448
475
|
|
449
476
|
##
|
@@ -452,7 +479,7 @@ module Signet
|
|
452
479
|
#
|
453
480
|
# @param [String] new_code
|
454
481
|
# The authorization code.
|
455
|
-
def code=
|
482
|
+
def code= new_code
|
456
483
|
@code = new_code
|
457
484
|
end
|
458
485
|
|
@@ -461,7 +488,7 @@ module Signet
|
|
461
488
|
#
|
462
489
|
# @return [String] The redirect URI.
|
463
490
|
def redirect_uri
|
464
|
-
|
491
|
+
@redirect_uri
|
465
492
|
end
|
466
493
|
|
467
494
|
##
|
@@ -469,14 +496,14 @@ module Signet
|
|
469
496
|
#
|
470
497
|
# @param [String] new_redirect_uri
|
471
498
|
# The redirect URI.
|
472
|
-
def redirect_uri=
|
473
|
-
new_redirect_uri = Addressable::URI.parse
|
474
|
-
#TODO - Better solution to allow google postmessage flow. For now, make an exception to the spec.
|
475
|
-
|
476
|
-
|
477
|
-
else
|
499
|
+
def redirect_uri= new_redirect_uri
|
500
|
+
new_redirect_uri = Addressable::URI.parse new_redirect_uri
|
501
|
+
# TODO: - Better solution to allow google postmessage flow. For now, make an exception to the spec.
|
502
|
+
unless new_redirect_uri.nil? || new_redirect_uri.absolute? || uri_is_postmessage?(new_redirect_uri) ||
|
503
|
+
uri_is_oob?(new_redirect_uri)
|
478
504
|
raise ArgumentError, "Redirect URI must be an absolute URI."
|
479
505
|
end
|
506
|
+
@redirect_uri = new_redirect_uri
|
480
507
|
end
|
481
508
|
|
482
509
|
##
|
@@ -485,7 +512,7 @@ module Signet
|
|
485
512
|
#
|
486
513
|
# @return [String] The username.
|
487
514
|
def username
|
488
|
-
|
515
|
+
@username
|
489
516
|
end
|
490
517
|
|
491
518
|
##
|
@@ -494,7 +521,7 @@ module Signet
|
|
494
521
|
#
|
495
522
|
# @param [String] new_username
|
496
523
|
# The username.
|
497
|
-
def username=
|
524
|
+
def username= new_username
|
498
525
|
@username = new_username
|
499
526
|
end
|
500
527
|
|
@@ -504,7 +531,7 @@ module Signet
|
|
504
531
|
#
|
505
532
|
# @return [String] The password.
|
506
533
|
def password
|
507
|
-
|
534
|
+
@password
|
508
535
|
end
|
509
536
|
|
510
537
|
##
|
@@ -513,7 +540,7 @@ module Signet
|
|
513
540
|
#
|
514
541
|
# @param [String] new_password
|
515
542
|
# The password.
|
516
|
-
def password=
|
543
|
+
def password= new_password
|
517
544
|
@password = new_password
|
518
545
|
end
|
519
546
|
|
@@ -523,7 +550,7 @@ module Signet
|
|
523
550
|
#
|
524
551
|
# @return [String] Issuer id.
|
525
552
|
def issuer
|
526
|
-
|
553
|
+
@issuer
|
527
554
|
end
|
528
555
|
|
529
556
|
##
|
@@ -532,17 +559,17 @@ module Signet
|
|
532
559
|
#
|
533
560
|
# @param [String] new_issuer
|
534
561
|
# Issuer ID (typical in email adddress form).
|
535
|
-
def issuer=
|
562
|
+
def issuer= new_issuer
|
536
563
|
@issuer = new_issuer
|
537
564
|
end
|
538
565
|
|
539
566
|
##
|
540
|
-
# Returns the
|
567
|
+
# Returns the target audience ID when issuing assertions.
|
541
568
|
# Used only by the assertion grant type.
|
542
569
|
#
|
543
570
|
# @return [String] Target audience ID.
|
544
571
|
def audience
|
545
|
-
|
572
|
+
@audience
|
546
573
|
end
|
547
574
|
|
548
575
|
##
|
@@ -551,7 +578,7 @@ module Signet
|
|
551
578
|
#
|
552
579
|
# @param [String] new_audience
|
553
580
|
# Target audience ID
|
554
|
-
def audience=
|
581
|
+
def audience= new_audience
|
555
582
|
@audience = new_audience
|
556
583
|
end
|
557
584
|
|
@@ -561,7 +588,7 @@ module Signet
|
|
561
588
|
#
|
562
589
|
# @return [String] Target user for impersonation.
|
563
590
|
def principal
|
564
|
-
|
591
|
+
@principal
|
565
592
|
end
|
566
593
|
|
567
594
|
##
|
@@ -570,12 +597,12 @@ module Signet
|
|
570
597
|
#
|
571
598
|
# @param [String] new_person
|
572
599
|
# Target user for impersonation
|
573
|
-
def principal=
|
600
|
+
def principal= new_person
|
574
601
|
@principal = new_person
|
575
602
|
end
|
576
603
|
|
577
|
-
|
578
|
-
|
604
|
+
alias person principal
|
605
|
+
alias person= principal=
|
579
606
|
|
580
607
|
##
|
581
608
|
# The target "sub" when issuing assertions.
|
@@ -589,7 +616,7 @@ module Signet
|
|
589
616
|
#
|
590
617
|
# @return [Integer] Assertion expiry, in seconds
|
591
618
|
def expiry
|
592
|
-
|
619
|
+
@expiry
|
593
620
|
end
|
594
621
|
|
595
622
|
##
|
@@ -598,18 +625,17 @@ module Signet
|
|
598
625
|
#
|
599
626
|
# @param [Integer, String] new_expiry
|
600
627
|
# Assertion expiry, in seconds
|
601
|
-
def expiry=
|
628
|
+
def expiry= new_expiry
|
602
629
|
@expiry = new_expiry ? new_expiry.to_i : nil
|
603
630
|
end
|
604
631
|
|
605
|
-
|
606
632
|
##
|
607
633
|
# Returns the signing key associated with this client.
|
608
634
|
# Used only by the assertion grant type.
|
609
635
|
#
|
610
636
|
# @return [String,OpenSSL::PKey] Signing key
|
611
637
|
def signing_key
|
612
|
-
|
638
|
+
@signing_key
|
613
639
|
end
|
614
640
|
|
615
641
|
##
|
@@ -618,7 +644,7 @@ module Signet
|
|
618
644
|
#
|
619
645
|
# @param [String, OpenSSL::Pkey] new_key
|
620
646
|
# Signing key. Either private key for RSA or string for HMAC algorithm
|
621
|
-
def signing_key=
|
647
|
+
def signing_key= new_key
|
622
648
|
@signing_key = new_key
|
623
649
|
end
|
624
650
|
|
@@ -626,7 +652,7 @@ module Signet
|
|
626
652
|
# Algorithm used for signing JWTs
|
627
653
|
# @return [String] Signing algorithm
|
628
654
|
def signing_algorithm
|
629
|
-
|
655
|
+
signing_key.is_a?(String) ? "HS256" : "RS256"
|
630
656
|
end
|
631
657
|
|
632
658
|
##
|
@@ -635,7 +661,7 @@ module Signet
|
|
635
661
|
#
|
636
662
|
# @return [Hash] The extension parameters.
|
637
663
|
def extension_parameters
|
638
|
-
|
664
|
+
@extension_parameters ||= {}
|
639
665
|
end
|
640
666
|
|
641
667
|
##
|
@@ -644,12 +670,12 @@ module Signet
|
|
644
670
|
#
|
645
671
|
# @param [Hash] new_extension_parameters
|
646
672
|
# The parameters.
|
647
|
-
def extension_parameters=
|
648
|
-
if new_extension_parameters.respond_to?
|
673
|
+
def extension_parameters= new_extension_parameters
|
674
|
+
if new_extension_parameters.respond_to? :to_hash
|
649
675
|
@extension_parameters = new_extension_parameters.to_hash
|
650
676
|
else
|
651
677
|
raise TypeError,
|
652
|
-
|
678
|
+
"Expected Hash, got #{new_extension_parameters.class}."
|
653
679
|
end
|
654
680
|
end
|
655
681
|
|
@@ -658,7 +684,7 @@ module Signet
|
|
658
684
|
#
|
659
685
|
# @return [Hash] The pass through parameters.
|
660
686
|
def additional_parameters
|
661
|
-
|
687
|
+
@additional_parameters ||= {}
|
662
688
|
end
|
663
689
|
|
664
690
|
##
|
@@ -666,8 +692,8 @@ module Signet
|
|
666
692
|
#
|
667
693
|
# @param [Hash] new_additional_parameters
|
668
694
|
# The parameters.
|
669
|
-
def additional_parameters=
|
670
|
-
if new_additional_parameters.respond_to?
|
695
|
+
def additional_parameters= new_additional_parameters
|
696
|
+
if new_additional_parameters.respond_to? :to_hash
|
671
697
|
@additional_parameters = new_additional_parameters.to_hash
|
672
698
|
else
|
673
699
|
raise TypeError,
|
@@ -680,7 +706,7 @@ module Signet
|
|
680
706
|
#
|
681
707
|
# @return [String] The refresh token.
|
682
708
|
def refresh_token
|
683
|
-
|
709
|
+
@refresh_token ||= nil
|
684
710
|
end
|
685
711
|
|
686
712
|
##
|
@@ -688,7 +714,7 @@ module Signet
|
|
688
714
|
#
|
689
715
|
# @param [String] new_refresh_token
|
690
716
|
# The refresh token.
|
691
|
-
def refresh_token=
|
717
|
+
def refresh_token= new_refresh_token
|
692
718
|
@refresh_token = new_refresh_token
|
693
719
|
end
|
694
720
|
|
@@ -697,7 +723,7 @@ module Signet
|
|
697
723
|
#
|
698
724
|
# @return [String] The access token.
|
699
725
|
def access_token
|
700
|
-
|
726
|
+
@access_token ||= nil
|
701
727
|
end
|
702
728
|
|
703
729
|
##
|
@@ -705,7 +731,7 @@ module Signet
|
|
705
731
|
#
|
706
732
|
# @param [String] new_access_token
|
707
733
|
# The access token.
|
708
|
-
def access_token=
|
734
|
+
def access_token= new_access_token
|
709
735
|
@access_token = new_access_token
|
710
736
|
end
|
711
737
|
|
@@ -714,7 +740,7 @@ module Signet
|
|
714
740
|
#
|
715
741
|
# @return [String] The ID token.
|
716
742
|
def id_token
|
717
|
-
|
743
|
+
@id_token ||= nil
|
718
744
|
end
|
719
745
|
|
720
746
|
##
|
@@ -722,7 +748,7 @@ module Signet
|
|
722
748
|
#
|
723
749
|
# @param [String] new_id_token
|
724
750
|
# The ID token.
|
725
|
-
def id_token=
|
751
|
+
def id_token= new_id_token
|
726
752
|
@id_token = new_id_token
|
727
753
|
end
|
728
754
|
|
@@ -734,17 +760,16 @@ module Signet
|
|
734
760
|
# omitted.
|
735
761
|
#
|
736
762
|
# @return [String] The decoded ID token.
|
737
|
-
def decoded_id_token public_key=nil, options = {}, &keyfinder
|
763
|
+
def decoded_id_token public_key = nil, options = {}, &keyfinder
|
738
764
|
options[:algorithm] ||= signing_algorithm
|
739
|
-
verify =
|
740
|
-
payload, _header = JWT.decode(
|
741
|
-
|
742
|
-
|
743
|
-
elsif payload['aud'] != self.client_id
|
765
|
+
verify = !public_key.nil? || block_given?
|
766
|
+
payload, _header = JWT.decode(id_token, public_key, verify, options, &keyfinder)
|
767
|
+
raise Signet::UnsafeOperationError, "No ID token audience declared." unless payload.key? "aud"
|
768
|
+
unless Array(payload["aud"]).include?(client_id)
|
744
769
|
raise Signet::UnsafeOperationError,
|
745
|
-
|
770
|
+
"ID token audience did not match Client ID."
|
746
771
|
end
|
747
|
-
|
772
|
+
payload
|
748
773
|
end
|
749
774
|
|
750
775
|
##
|
@@ -790,8 +815,8 @@ module Signet
|
|
790
815
|
#
|
791
816
|
# @param [String,Integer,Time] new_issued_at
|
792
817
|
# The access token issuance time.
|
793
|
-
def issued_at=
|
794
|
-
@issued_at = normalize_timestamp
|
818
|
+
def issued_at= new_issued_at
|
819
|
+
@issued_at = normalize_timestamp new_issued_at
|
795
820
|
end
|
796
821
|
|
797
822
|
##
|
@@ -809,7 +834,7 @@ module Signet
|
|
809
834
|
# not expire.
|
810
835
|
# @param [String,Integer,Time, nil] new_expires_at
|
811
836
|
# The access token expiration time.
|
812
|
-
def expires_at=
|
837
|
+
def expires_at= new_expires_at
|
813
838
|
@expires_at = normalize_timestamp new_expires_at
|
814
839
|
end
|
815
840
|
|
@@ -820,7 +845,7 @@ module Signet
|
|
820
845
|
# @return [TrueClass, FalseClass]
|
821
846
|
# The expiration state of the access token.
|
822
847
|
def expired?
|
823
|
-
|
848
|
+
!expires_at.nil? && Time.now >= expires_at
|
824
849
|
end
|
825
850
|
|
826
851
|
##
|
@@ -832,8 +857,8 @@ module Signet
|
|
832
857
|
# expired.
|
833
858
|
# @return [TrueClass, FalseClass]
|
834
859
|
# The expiration state of the access token.
|
835
|
-
def expires_within?
|
836
|
-
|
860
|
+
def expires_within? sec
|
861
|
+
!expires_at.nil? && Time.now >= (expires_at - sec)
|
837
862
|
end
|
838
863
|
|
839
864
|
##
|
@@ -849,7 +874,6 @@ module Signet
|
|
849
874
|
@expires_at = nil
|
850
875
|
end
|
851
876
|
|
852
|
-
|
853
877
|
##
|
854
878
|
# Returns the inferred grant type, based on the current state of the
|
855
879
|
# client object. Returns `"none"` if the client has insufficient
|
@@ -859,52 +883,47 @@ module Signet
|
|
859
883
|
# The inferred grant type.
|
860
884
|
def grant_type
|
861
885
|
@grant_type ||= nil
|
862
|
-
if @grant_type
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
elsif self.issuer && self.signing_key
|
872
|
-
'urn:ietf:params:oauth:grant-type:jwt-bearer'
|
873
|
-
else
|
874
|
-
# We don't have sufficient auth information, assume an out-of-band
|
875
|
-
# authorization arrangement between the client and server, or an
|
876
|
-
# extension grant type.
|
877
|
-
nil
|
878
|
-
end
|
886
|
+
return @grant_type if @grant_type
|
887
|
+
if code && redirect_uri
|
888
|
+
"authorization_code"
|
889
|
+
elsif refresh_token
|
890
|
+
"refresh_token"
|
891
|
+
elsif username && password
|
892
|
+
"password"
|
893
|
+
elsif issuer && signing_key
|
894
|
+
"urn:ietf:params:oauth:grant-type:jwt-bearer"
|
879
895
|
end
|
880
896
|
end
|
881
897
|
|
882
|
-
def grant_type=
|
898
|
+
def grant_type= new_grant_type
|
883
899
|
case new_grant_type
|
884
|
-
when
|
885
|
-
|
900
|
+
when "authorization_code", "refresh_token",
|
901
|
+
"password", "client_credentials"
|
886
902
|
@grant_type = new_grant_type
|
887
903
|
else
|
888
|
-
@grant_type = Addressable::URI.parse
|
904
|
+
@grant_type = Addressable::URI.parse new_grant_type
|
889
905
|
end
|
890
906
|
end
|
891
907
|
|
892
|
-
def to_jwt
|
893
|
-
options = deep_hash_normalize
|
908
|
+
def to_jwt options = {}
|
909
|
+
options = deep_hash_normalize options
|
894
910
|
|
895
911
|
now = Time.new
|
896
912
|
skew = options[:skew] || 60
|
897
913
|
assertion = {
|
898
|
-
"iss" =>
|
899
|
-
"aud" =>
|
900
|
-
"exp" => (now +
|
914
|
+
"iss" => issuer,
|
915
|
+
"aud" => audience,
|
916
|
+
"exp" => (now + expiry).to_i,
|
901
917
|
"iat" => (now - skew).to_i
|
902
918
|
}
|
903
|
-
assertion[
|
904
|
-
assertion[
|
905
|
-
assertion[
|
906
|
-
|
919
|
+
assertion["scope"] = scope.join " " unless scope.nil?
|
920
|
+
assertion["target_audience"] = target_audience unless target_audience.nil?
|
921
|
+
assertion["prn"] = person unless person.nil?
|
922
|
+
assertion["sub"] = sub unless sub.nil?
|
923
|
+
JWT.encode assertion, signing_key, signing_algorithm
|
907
924
|
end
|
925
|
+
# rubocop:disable Style/MethodDefParentheses
|
926
|
+
# rubocop:disable Metrics/AbcSize
|
908
927
|
|
909
928
|
##
|
910
929
|
# Serialize the client object to JSON.
|
@@ -913,29 +932,34 @@ module Signet
|
|
913
932
|
#
|
914
933
|
# @return [String] A serialized JSON representation of the client.
|
915
934
|
def to_json(*)
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
935
|
+
MultiJson.dump(
|
936
|
+
"authorization_uri" => authorization_uri ? authorization_uri.to_s : nil,
|
937
|
+
"token_credential_uri" => token_credential_uri ? token_credential_uri.to_s : nil,
|
938
|
+
"client_id" => client_id,
|
939
|
+
"client_secret" => client_secret,
|
940
|
+
"scope" => scope,
|
941
|
+
"target_audience" => target_audience,
|
942
|
+
"state" => state,
|
943
|
+
"code" => code,
|
944
|
+
"redirect_uri" => redirect_uri ? redirect_uri.to_s : nil,
|
945
|
+
"username" => username,
|
946
|
+
"password" => password,
|
947
|
+
"issuer" => issuer,
|
948
|
+
"audience" => audience,
|
949
|
+
"person" => person,
|
950
|
+
"expiry" => expiry,
|
951
|
+
"expires_at" => expires_at ? expires_at.to_i : nil,
|
952
|
+
"signing_key" => signing_key,
|
953
|
+
"refresh_token" => refresh_token,
|
954
|
+
"access_token" => access_token,
|
955
|
+
"id_token" => id_token,
|
956
|
+
"extension_parameters" => extension_parameters
|
957
|
+
)
|
938
958
|
end
|
959
|
+
# rubocop:enable Style/MethodDefParentheses
|
960
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
961
|
+
# rubocop:disable Metrics/MethodLength
|
962
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
939
963
|
|
940
964
|
##
|
941
965
|
# Generates a request for token credentials.
|
@@ -947,58 +971,58 @@ module Signet
|
|
947
971
|
#
|
948
972
|
# @private
|
949
973
|
# @return [Array] The request object.
|
950
|
-
def generate_access_token_request
|
951
|
-
options = deep_hash_normalize
|
952
|
-
|
953
|
-
parameters = {"grant_type" =>
|
954
|
-
case
|
955
|
-
when
|
956
|
-
parameters[
|
957
|
-
parameters[
|
958
|
-
when
|
959
|
-
parameters[
|
960
|
-
parameters[
|
961
|
-
when
|
962
|
-
parameters[
|
963
|
-
when
|
964
|
-
parameters[
|
974
|
+
def generate_access_token_request options = {}
|
975
|
+
options = deep_hash_normalize options
|
976
|
+
|
977
|
+
parameters = { "grant_type" => grant_type }
|
978
|
+
case grant_type
|
979
|
+
when "authorization_code"
|
980
|
+
parameters["code"] = code
|
981
|
+
parameters["redirect_uri"] = redirect_uri
|
982
|
+
when "password"
|
983
|
+
parameters["username"] = username
|
984
|
+
parameters["password"] = password
|
985
|
+
when "refresh_token"
|
986
|
+
parameters["refresh_token"] = refresh_token
|
987
|
+
when "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
988
|
+
parameters["assertion"] = to_jwt options
|
965
989
|
else
|
966
|
-
if
|
990
|
+
if redirect_uri
|
967
991
|
# Grant type was intended to be `authorization_code` because of
|
968
992
|
# the presence of the redirect URI.
|
969
|
-
raise ArgumentError,
|
993
|
+
raise ArgumentError, "Missing authorization code."
|
970
994
|
end
|
971
|
-
parameters.merge!
|
995
|
+
parameters.merge! extension_parameters
|
972
996
|
end
|
973
|
-
parameters[
|
974
|
-
parameters[
|
997
|
+
parameters["client_id"] = client_id unless client_id.nil?
|
998
|
+
parameters["client_secret"] = client_secret unless client_secret.nil?
|
975
999
|
if options[:scope]
|
976
|
-
parameters[
|
977
|
-
elsif options[:use_configured_scope] && !
|
978
|
-
parameters[
|
1000
|
+
parameters["scope"] = options[:scope]
|
1001
|
+
elsif options[:use_configured_scope] && !scope.nil?
|
1002
|
+
parameters["scope"] = scope
|
979
1003
|
end
|
980
|
-
additional =
|
1004
|
+
additional = additional_parameters.merge(options[:additional_parameters] || {})
|
981
1005
|
additional.each { |k, v| parameters[k.to_s] = v }
|
982
1006
|
parameters
|
983
1007
|
end
|
1008
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
1009
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
984
1010
|
|
985
|
-
def fetch_access_token
|
986
|
-
if
|
987
|
-
raise ArgumentError, 'Missing token endpoint URI.'
|
988
|
-
end
|
1011
|
+
def fetch_access_token options = {}
|
1012
|
+
raise ArgumentError, "Missing token endpoint URI." if token_credential_uri.nil?
|
989
1013
|
|
990
|
-
options = deep_hash_normalize
|
1014
|
+
options = deep_hash_normalize options
|
991
1015
|
|
992
1016
|
client = options[:connection] ||= Faraday.default_connection
|
993
|
-
url = Addressable::URI.parse(
|
994
|
-
parameters =
|
995
|
-
if client.is_a?
|
1017
|
+
url = Addressable::URI.parse(token_credential_uri).normalize.to_s
|
1018
|
+
parameters = generate_access_token_request options
|
1019
|
+
if client.is_a? Faraday::Connection
|
996
1020
|
response = client.post url,
|
997
|
-
|
998
|
-
|
1021
|
+
Addressable::URI.form_encode(parameters),
|
1022
|
+
"Content-Type" => "application/x-www-form-urlencoded"
|
999
1023
|
status = response.status.to_i
|
1000
1024
|
body = response.body
|
1001
|
-
content_type = response.headers[
|
1025
|
+
content_type = response.headers["Content-type"]
|
1002
1026
|
else
|
1003
1027
|
# Hurley
|
1004
1028
|
response = client.post url, parameters
|
@@ -1007,49 +1031,46 @@ module Signet
|
|
1007
1031
|
content_type = response.header[:content_type]
|
1008
1032
|
end
|
1009
1033
|
|
1010
|
-
if status == 200
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
message += " Server message:\n#{response.body.to_s.strip}"
|
1016
|
-
end
|
1034
|
+
return ::Signet::OAuth2.parse_credentials body, content_type if status == 200
|
1035
|
+
|
1036
|
+
message = " Server message:\n#{response.body.to_s.strip}" unless body.to_s.strip.empty?
|
1037
|
+
if [400, 401, 403].include? status
|
1038
|
+
message = "Authorization failed." + message
|
1017
1039
|
raise ::Signet::AuthorizationError.new(
|
1018
|
-
message, :
|
1040
|
+
message, response: response
|
1019
1041
|
)
|
1020
1042
|
elsif status.to_s[0] == "5"
|
1021
|
-
message =
|
1022
|
-
|
1023
|
-
message += " Server message:\n#{response.body.to_s.strip}"
|
1024
|
-
end
|
1025
|
-
raise ::Signet::RemoteServerError.new(message)
|
1043
|
+
message = "Remote server error." + message
|
1044
|
+
raise ::Signet::RemoteServerError, message
|
1026
1045
|
else
|
1027
|
-
message = "Unexpected status code: #{response.status}."
|
1028
|
-
|
1029
|
-
message += " Server message:\n#{response.body.to_s.strip}"
|
1030
|
-
end
|
1031
|
-
raise ::Signet::UnexpectedStatusError.new(message)
|
1046
|
+
message = "Unexpected status code: #{response.status}." + message
|
1047
|
+
raise ::Signet::UnexpectedStatusError, message
|
1032
1048
|
end
|
1033
1049
|
end
|
1050
|
+
# rubocop:enable Metrics/AbcSize
|
1051
|
+
# rubocop:enable Metrics/MethodLength
|
1034
1052
|
|
1035
|
-
def fetch_access_token!
|
1036
|
-
token_hash =
|
1053
|
+
def fetch_access_token! options = {}
|
1054
|
+
token_hash = fetch_access_token options
|
1037
1055
|
if token_hash
|
1038
1056
|
# No-op for grant types other than `authorization_code`.
|
1039
1057
|
# An authorization code is a one-time use token and is immediately
|
1040
1058
|
# revoked after usage.
|
1041
1059
|
self.code = nil
|
1042
1060
|
self.issued_at = Time.now
|
1043
|
-
|
1061
|
+
update_token! token_hash
|
1044
1062
|
end
|
1045
|
-
|
1063
|
+
token_hash
|
1046
1064
|
end
|
1047
1065
|
|
1048
1066
|
##
|
1049
1067
|
# Refresh the access token, if possible
|
1050
|
-
def refresh!
|
1051
|
-
|
1068
|
+
def refresh! options = {}
|
1069
|
+
fetch_access_token! options
|
1052
1070
|
end
|
1071
|
+
# rubocop:disable Metrics/AbcSize
|
1072
|
+
# rubocop:disable Metrics/MethodLength
|
1073
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
1053
1074
|
|
1054
1075
|
##
|
1055
1076
|
# Generates an authenticated request for protected resources.
|
@@ -1071,55 +1092,54 @@ module Signet
|
|
1071
1092
|
# - <code>:realm</code> -
|
1072
1093
|
# The Authorization realm. See RFC 2617.
|
1073
1094
|
# @return [Faraday::Request] The request object.
|
1074
|
-
def generate_authenticated_request
|
1075
|
-
options = deep_hash_normalize
|
1095
|
+
def generate_authenticated_request options = {}
|
1096
|
+
options = deep_hash_normalize options
|
1076
1097
|
|
1077
|
-
|
1078
|
-
raise ArgumentError, 'Missing access token.'
|
1079
|
-
end
|
1098
|
+
raise ArgumentError, "Missing access token." if access_token.nil?
|
1080
1099
|
options = {
|
1081
|
-
:
|
1100
|
+
realm: nil
|
1082
1101
|
}.merge(options)
|
1083
1102
|
|
1084
|
-
if options[:request].
|
1103
|
+
if options[:request].is_a? Faraday::Request
|
1085
1104
|
request = options[:request]
|
1086
1105
|
else
|
1087
|
-
if options[:request].
|
1106
|
+
if options[:request].is_a? Array
|
1088
1107
|
method, uri, headers, body = options[:request]
|
1089
1108
|
else
|
1090
1109
|
method = options[:method] || :get
|
1091
1110
|
uri = options[:uri]
|
1092
1111
|
headers = options[:headers] || []
|
1093
|
-
body = options[:body] ||
|
1112
|
+
body = options[:body] || ""
|
1094
1113
|
end
|
1095
|
-
headers = headers.to_a if headers.
|
1114
|
+
headers = headers.to_a if headers.is_a? Hash
|
1096
1115
|
request_components = {
|
1097
|
-
:method
|
1098
|
-
:uri
|
1099
|
-
:
|
1100
|
-
:body
|
1116
|
+
method: method,
|
1117
|
+
uri: uri,
|
1118
|
+
headers: headers,
|
1119
|
+
body: body
|
1101
1120
|
}
|
1102
1121
|
# Verify that we have all pieces required to return an HTTP request
|
1103
1122
|
request_components.each do |(key, value)|
|
1104
|
-
unless value
|
1105
|
-
raise ArgumentError, "Missing :#{key} parameter."
|
1106
|
-
end
|
1123
|
+
raise ArgumentError, "Missing :#{key} parameter." unless value
|
1107
1124
|
end
|
1108
1125
|
method = method.to_s.downcase.to_sym
|
1109
|
-
request = options[:connection].build_request
|
1110
|
-
req.url
|
1111
|
-
req.headers = Faraday::Utils::Headers.new
|
1126
|
+
request = options[:connection].build_request method.to_s.downcase.to_sym do |req|
|
1127
|
+
req.url Addressable::URI.parse(uri).normalize.to_s
|
1128
|
+
req.headers = Faraday::Utils::Headers.new headers
|
1112
1129
|
req.body = body
|
1113
1130
|
end
|
1114
1131
|
end
|
1115
1132
|
|
1116
|
-
request[
|
1117
|
-
|
1118
|
-
options[:realm] ? [[
|
1133
|
+
request["Authorization"] = ::Signet::OAuth2.generate_bearer_authorization_header(
|
1134
|
+
access_token,
|
1135
|
+
options[:realm] ? [["realm", options[:realm]]] : nil
|
1119
1136
|
)
|
1120
|
-
request[
|
1121
|
-
|
1137
|
+
request["Cache-Control"] = "no-store"
|
1138
|
+
request
|
1122
1139
|
end
|
1140
|
+
# rubocop:enable Metrics/AbcSize
|
1141
|
+
# rubocop:enable Metrics/MethodLength
|
1142
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
1123
1143
|
|
1124
1144
|
##
|
1125
1145
|
# Transmits a request for a protected resource.
|
@@ -1151,27 +1171,22 @@ module Signet
|
|
1151
1171
|
# )
|
1152
1172
|
#
|
1153
1173
|
# @return [Array] The response object.
|
1154
|
-
def fetch_protected_resource
|
1155
|
-
options = deep_hash_normalize
|
1174
|
+
def fetch_protected_resource options = {}
|
1175
|
+
options = deep_hash_normalize options
|
1156
1176
|
|
1157
1177
|
options[:connection] ||= Faraday.default_connection
|
1158
|
-
request =
|
1159
|
-
request_env = request.to_env
|
1178
|
+
request = generate_authenticated_request options
|
1179
|
+
request_env = request.to_env options[:connection]
|
1160
1180
|
request_env[:request] ||= request
|
1161
|
-
response = options[:connection].app.call
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
message, :request => request, :response => response
|
1171
|
-
)
|
1172
|
-
else
|
1173
|
-
return response
|
1174
|
-
end
|
1181
|
+
response = options[:connection].app.call request_env
|
1182
|
+
return response unless response.status.to_i == 401
|
1183
|
+
# When accessing a protected resource, we only want to raise an
|
1184
|
+
# error for 401 responses.
|
1185
|
+
message = "Authorization failed."
|
1186
|
+
message += " Server message:\n#{response.body.to_s.strip}" unless response.body.to_s.strip.empty?
|
1187
|
+
raise ::Signet::AuthorizationError.new(
|
1188
|
+
message, request: request, response: response
|
1189
|
+
)
|
1175
1190
|
end
|
1176
1191
|
|
1177
1192
|
private
|
@@ -1179,33 +1194,33 @@ module Signet
|
|
1179
1194
|
##
|
1180
1195
|
# Check if URI is Google's postmessage flow (not a valid redirect_uri by spec, but allowed)
|
1181
1196
|
# @private
|
1182
|
-
def uri_is_postmessage?
|
1183
|
-
|
1197
|
+
def uri_is_postmessage? uri
|
1198
|
+
uri.to_s.casecmp("postmessage").zero?
|
1184
1199
|
end
|
1185
1200
|
|
1186
1201
|
##
|
1187
1202
|
# Check if the URI is a out-of-band
|
1188
1203
|
# @private
|
1189
|
-
def uri_is_oob?
|
1190
|
-
|
1204
|
+
def uri_is_oob? uri
|
1205
|
+
OOB_MODES.include? uri.to_s
|
1191
1206
|
end
|
1192
1207
|
|
1193
1208
|
# Convert all keys in this hash (nested) to symbols for uniform retrieval
|
1194
|
-
def recursive_hash_normalize_keys
|
1209
|
+
def recursive_hash_normalize_keys val
|
1195
1210
|
if val.is_a? Hash
|
1196
|
-
deep_hash_normalize
|
1211
|
+
deep_hash_normalize val
|
1197
1212
|
else
|
1198
1213
|
val
|
1199
1214
|
end
|
1200
1215
|
end
|
1201
1216
|
|
1202
|
-
def deep_hash_normalize
|
1217
|
+
def deep_hash_normalize old_hash
|
1203
1218
|
sym_hash = {}
|
1204
|
-
old_hash
|
1219
|
+
old_hash&.each { |k, v| sym_hash[k.to_sym] = recursive_hash_normalize_keys v }
|
1205
1220
|
sym_hash
|
1206
1221
|
end
|
1207
1222
|
|
1208
|
-
def normalize_timestamp
|
1223
|
+
def normalize_timestamp time
|
1209
1224
|
case time
|
1210
1225
|
when NilClass
|
1211
1226
|
nil
|
@@ -1214,15 +1229,15 @@ module Signet
|
|
1214
1229
|
when DateTime
|
1215
1230
|
time.to_time
|
1216
1231
|
when String
|
1217
|
-
Time.parse
|
1232
|
+
Time.parse time
|
1218
1233
|
when Integer
|
1219
|
-
Time.at
|
1234
|
+
Time.at time
|
1220
1235
|
else
|
1221
|
-
|
1236
|
+
raise "Invalid time value #{time}"
|
1222
1237
|
end
|
1223
1238
|
end
|
1224
1239
|
|
1225
|
-
def set_relative_expires_at
|
1240
|
+
def set_relative_expires_at issued_at, expires_in
|
1226
1241
|
self.issued_at = issued_at
|
1227
1242
|
# Using local expires_in because if self.expires_in is used, it returns
|
1228
1243
|
# the time left before the token expires
|