signet 0.11.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|