googleauth 0.11.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +7 -0
- data/.github/workflows/release.yml +36 -0
- data/.rubocop.yml +3 -1
- data/CHANGELOG.md +59 -23
- data/Gemfile +5 -2
- data/{COPYING → LICENSE} +0 -0
- data/Rakefile +21 -0
- data/googleauth.gemspec +3 -2
- data/integration/helper.rb +31 -0
- data/integration/id_tokens/key_source_test.rb +74 -0
- data/lib/googleauth.rb +1 -0
- data/lib/googleauth/application_default.rb +1 -1
- data/lib/googleauth/compute_engine.rb +40 -9
- data/lib/googleauth/credentials.rb +217 -54
- data/lib/googleauth/id_tokens.rb +233 -0
- data/lib/googleauth/id_tokens/errors.rb +71 -0
- data/lib/googleauth/id_tokens/key_sources.rb +394 -0
- data/lib/googleauth/id_tokens/verifier.rb +144 -0
- data/lib/googleauth/json_key_reader.rb +6 -2
- data/lib/googleauth/service_account.rb +39 -20
- data/lib/googleauth/signet.rb +3 -2
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +3 -6
- data/spec/googleauth/apply_auth_examples.rb +28 -5
- data/spec/googleauth/compute_engine_spec.rb +66 -13
- data/spec/googleauth/credentials_spec.rb +240 -112
- data/spec/googleauth/service_account_spec.rb +31 -16
- data/spec/googleauth/signet_spec.rb +15 -7
- data/spec/googleauth/user_refresh_spec.rb +1 -1
- data/test/helper.rb +33 -0
- data/test/id_tokens/key_sources_test.rb +240 -0
- data/test/id_tokens/verifier_test.rb +269 -0
- metadata +18 -7
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2020 Google LLC
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are
|
7
|
+
# met:
|
8
|
+
#
|
9
|
+
# * Redistributions of source code must retain the above copyright
|
10
|
+
# notice, this list of conditions and the following disclaimer.
|
11
|
+
# * Redistributions in binary form must reproduce the above
|
12
|
+
# copyright notice, this list of conditions and the following disclaimer
|
13
|
+
# in the documentation and/or other materials provided with the
|
14
|
+
# distribution.
|
15
|
+
# * Neither the name of Google Inc. nor the names of its
|
16
|
+
# contributors may be used to endorse or promote products derived from
|
17
|
+
# this software without specific prior written permission.
|
18
|
+
#
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
20
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
21
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
22
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
23
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
24
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
25
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
26
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
27
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
28
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
29
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
30
|
+
|
31
|
+
require "jwt"
|
32
|
+
|
33
|
+
module Google
|
34
|
+
module Auth
|
35
|
+
module IDTokens
|
36
|
+
##
|
37
|
+
# An object that can verify ID tokens.
|
38
|
+
#
|
39
|
+
# A verifier maintains a set of default settings, including the key
|
40
|
+
# source and fields to verify. However, individual verification calls can
|
41
|
+
# override any of these settings.
|
42
|
+
#
|
43
|
+
class Verifier
|
44
|
+
##
|
45
|
+
# Create a verifier.
|
46
|
+
#
|
47
|
+
# @param key_source [key source] The default key source to use. All
|
48
|
+
# verification calls must have a key source, so if no default key
|
49
|
+
# source is provided here, then calls to {#verify} _must_ provide
|
50
|
+
# a key source.
|
51
|
+
# @param aud [String,nil] The default audience (`aud`) check, or `nil`
|
52
|
+
# for no check.
|
53
|
+
# @param azp [String,nil] The default authorized party (`azp`) check,
|
54
|
+
# or `nil` for no check.
|
55
|
+
# @param iss [String,nil] The default issuer (`iss`) check, or `nil`
|
56
|
+
# for no check.
|
57
|
+
#
|
58
|
+
def initialize key_source: nil,
|
59
|
+
aud: nil,
|
60
|
+
azp: nil,
|
61
|
+
iss: nil
|
62
|
+
@key_source = key_source
|
63
|
+
@aud = aud
|
64
|
+
@azp = azp
|
65
|
+
@iss = iss
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Verify the given token.
|
70
|
+
#
|
71
|
+
# @param token [String] the ID token to verify.
|
72
|
+
# @param key_source [key source] If given, override the key source.
|
73
|
+
# @param aud [String,nil] If given, override the `aud` check.
|
74
|
+
# @param azp [String,nil] If given, override the `azp` check.
|
75
|
+
# @param iss [String,nil] If given, override the `iss` check.
|
76
|
+
#
|
77
|
+
# @return [Hash] the decoded payload, if verification succeeded.
|
78
|
+
# @raise [KeySourceError] if the key source failed to obtain public keys
|
79
|
+
# @raise [VerificationError] if the token verification failed.
|
80
|
+
# Additional data may be available in the error subclass and message.
|
81
|
+
#
|
82
|
+
def verify token,
|
83
|
+
key_source: :default,
|
84
|
+
aud: :default,
|
85
|
+
azp: :default,
|
86
|
+
iss: :default
|
87
|
+
key_source = @key_source if key_source == :default
|
88
|
+
aud = @aud if aud == :default
|
89
|
+
azp = @azp if azp == :default
|
90
|
+
iss = @iss if iss == :default
|
91
|
+
|
92
|
+
raise KeySourceError, "No key sources" unless key_source
|
93
|
+
keys = key_source.current_keys
|
94
|
+
payload = decode_token token, keys, aud, azp, iss
|
95
|
+
unless payload
|
96
|
+
keys = key_source.refresh_keys
|
97
|
+
payload = decode_token token, keys, aud, azp, iss
|
98
|
+
end
|
99
|
+
raise SignatureError, "Token not verified as issued by Google" unless payload
|
100
|
+
payload
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def decode_token token, keys, aud, azp, iss
|
106
|
+
payload = nil
|
107
|
+
keys.find do |key|
|
108
|
+
begin
|
109
|
+
options = { algorithms: key.algorithm }
|
110
|
+
decoded_token = JWT.decode token, key.key, true, options
|
111
|
+
payload = decoded_token.first
|
112
|
+
rescue JWT::ExpiredSignature
|
113
|
+
raise ExpiredTokenError, "Token signature is expired"
|
114
|
+
rescue JWT::DecodeError
|
115
|
+
nil # Try the next key
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
normalize_and_verify_payload payload, aud, azp, iss
|
120
|
+
end
|
121
|
+
|
122
|
+
def normalize_and_verify_payload payload, aud, azp, iss
|
123
|
+
return nil unless payload
|
124
|
+
|
125
|
+
# Map the legacy "cid" claim to the canonical "azp"
|
126
|
+
payload["azp"] ||= payload["cid"] if payload.key? "cid"
|
127
|
+
|
128
|
+
# Payload content validation
|
129
|
+
if aud && (Array(aud) & Array(payload["aud"])).empty?
|
130
|
+
raise AudienceMismatchError, "Token aud mismatch: #{payload['aud']}"
|
131
|
+
end
|
132
|
+
if azp && (Array(azp) & Array(payload["azp"])).empty?
|
133
|
+
raise AuthorizedPartyMismatchError, "Token azp mismatch: #{payload['azp']}"
|
134
|
+
end
|
135
|
+
if iss && (Array(iss) & Array(payload["iss"])).empty?
|
136
|
+
raise IssuerMismatchError, "Token iss mismatch: #{payload['iss']}"
|
137
|
+
end
|
138
|
+
|
139
|
+
payload
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -38,8 +38,12 @@ module Google
|
|
38
38
|
json_key = MultiJson.load json_key_io.read
|
39
39
|
raise "missing client_email" unless json_key.key? "client_email"
|
40
40
|
raise "missing private_key" unless json_key.key? "private_key"
|
41
|
-
|
42
|
-
|
41
|
+
[
|
42
|
+
json_key["private_key"],
|
43
|
+
json_key["client_email"],
|
44
|
+
json_key["project_id"],
|
45
|
+
json_key["quota_project_id"]
|
46
|
+
]
|
43
47
|
end
|
44
48
|
end
|
45
49
|
end
|
@@ -51,28 +51,41 @@ module Google
|
|
51
51
|
extend CredentialsLoader
|
52
52
|
extend JsonKeyReader
|
53
53
|
attr_reader :project_id
|
54
|
+
attr_reader :quota_project_id
|
55
|
+
|
56
|
+
def enable_self_signed_jwt?
|
57
|
+
@enable_self_signed_jwt
|
58
|
+
end
|
54
59
|
|
55
60
|
# Creates a ServiceAccountCredentials.
|
56
61
|
#
|
57
62
|
# @param json_key_io [IO] an IO from which the JSON key can be read
|
58
63
|
# @param scope [string|array|nil] the scope(s) to access
|
59
64
|
def self.make_creds options = {}
|
60
|
-
json_key_io, scope
|
65
|
+
json_key_io, scope, enable_self_signed_jwt, target_audience, audience, token_credential_uri =
|
66
|
+
options.values_at :json_key_io, :scope, :enable_self_signed_jwt, :target_audience,
|
67
|
+
:audience, :token_credential_uri
|
68
|
+
raise ArgumentError, "Cannot specify both scope and target_audience" if scope && target_audience
|
69
|
+
|
61
70
|
if json_key_io
|
62
|
-
private_key, client_email, project_id = read_json_key json_key_io
|
71
|
+
private_key, client_email, project_id, quota_project_id = read_json_key json_key_io
|
63
72
|
else
|
64
73
|
private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
65
74
|
client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
66
75
|
project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
|
76
|
+
quota_project_id = nil
|
67
77
|
end
|
68
78
|
project_id ||= CredentialsLoader.load_gcloud_project_id
|
69
79
|
|
70
|
-
new(token_credential_uri: TOKEN_CRED_URI,
|
71
|
-
audience:
|
72
|
-
scope:
|
73
|
-
|
74
|
-
|
75
|
-
|
80
|
+
new(token_credential_uri: token_credential_uri || TOKEN_CRED_URI,
|
81
|
+
audience: audience || TOKEN_CRED_URI,
|
82
|
+
scope: scope,
|
83
|
+
enable_self_signed_jwt: enable_self_signed_jwt,
|
84
|
+
target_audience: target_audience,
|
85
|
+
issuer: client_email,
|
86
|
+
signing_key: OpenSSL::PKey::RSA.new(private_key),
|
87
|
+
project_id: project_id,
|
88
|
+
quota_project_id: quota_project_id)
|
76
89
|
.configure_connection(options)
|
77
90
|
end
|
78
91
|
|
@@ -87,30 +100,34 @@ module Google
|
|
87
100
|
|
88
101
|
def initialize options = {}
|
89
102
|
@project_id = options[:project_id]
|
103
|
+
@quota_project_id = options[:quota_project_id]
|
104
|
+
@enable_self_signed_jwt = options[:enable_self_signed_jwt] ? true : false
|
90
105
|
super options
|
91
106
|
end
|
92
107
|
|
93
|
-
# Extends the base class
|
94
|
-
#
|
95
|
-
# If scope(s) is not set, it creates a transient
|
96
|
-
# ServiceAccountJwtHeaderCredentials instance and uses that to
|
97
|
-
# authenticate instead.
|
108
|
+
# Extends the base class to use a transient
|
109
|
+
# ServiceAccountJwtHeaderCredentials for certain cases.
|
98
110
|
def apply! a_hash, opts = {}
|
99
|
-
# Use
|
100
|
-
|
111
|
+
# Use a self-singed JWT if there's no information that can be used to
|
112
|
+
# obtain an OAuth token, OR if there are scopes but also an assertion
|
113
|
+
# that they are default scopes that shouldn't be used to fetch a token.
|
114
|
+
if target_audience.nil? && (scope.nil? || enable_self_signed_jwt?)
|
115
|
+
apply_self_signed_jwt! a_hash
|
116
|
+
else
|
101
117
|
super
|
102
|
-
return
|
103
118
|
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
104
122
|
|
123
|
+
def apply_self_signed_jwt! a_hash
|
105
124
|
# Use the ServiceAccountJwtHeaderCredentials using the same cred values
|
106
|
-
# if no scopes are set.
|
107
125
|
cred_json = {
|
108
126
|
private_key: @signing_key.to_s,
|
109
127
|
client_email: @issuer
|
110
128
|
}
|
111
|
-
alt_clz = ServiceAccountJwtHeaderCredentials
|
112
129
|
key_io = StringIO.new MultiJson.dump(cred_json)
|
113
|
-
alt =
|
130
|
+
alt = ServiceAccountJwtHeaderCredentials.make_creds json_key_io: key_io
|
114
131
|
alt.apply! a_hash
|
115
132
|
end
|
116
133
|
end
|
@@ -133,6 +150,7 @@ module Google
|
|
133
150
|
extend CredentialsLoader
|
134
151
|
extend JsonKeyReader
|
135
152
|
attr_reader :project_id
|
153
|
+
attr_reader :quota_project_id
|
136
154
|
|
137
155
|
# make_creds proxies the construction of a credentials instance
|
138
156
|
#
|
@@ -151,12 +169,13 @@ module Google
|
|
151
169
|
def initialize options = {}
|
152
170
|
json_key_io = options[:json_key_io]
|
153
171
|
if json_key_io
|
154
|
-
@private_key, @issuer, @project_id =
|
172
|
+
@private_key, @issuer, @project_id, @quota_project_id =
|
155
173
|
self.class.read_json_key json_key_io
|
156
174
|
else
|
157
175
|
@private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
158
176
|
@issuer = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
159
177
|
@project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
|
178
|
+
@quota_project_id = nil
|
160
179
|
end
|
161
180
|
@project_id ||= CredentialsLoader.load_gcloud_project_id
|
162
181
|
@signing_key = OpenSSL::PKey::RSA.new @private_key
|
data/lib/googleauth/signet.rb
CHANGED
@@ -48,8 +48,9 @@ module Signet
|
|
48
48
|
def apply! a_hash, opts = {}
|
49
49
|
# fetch the access token there is currently not one, or if the client
|
50
50
|
# has expired
|
51
|
-
|
52
|
-
|
51
|
+
token_type = target_audience ? :id_token : :access_token
|
52
|
+
fetch_access_token! opts if send(token_type).nil? || expires_within?(60)
|
53
|
+
a_hash[AUTH_METADATA_KEY] = "Bearer #{send token_type}"
|
53
54
|
end
|
54
55
|
|
55
56
|
# Returns a clone of a_hash updated with the authentication token
|
data/lib/googleauth/version.rb
CHANGED
@@ -58,12 +58,9 @@ module Google
|
|
58
58
|
# end
|
59
59
|
#
|
60
60
|
# Instead of implementing the callback directly, applications are
|
61
|
-
# encouraged to use {Google::Auth::
|
61
|
+
# encouraged to use {Google::Auth::WebUserAuthorizer::CallbackApp} instead.
|
62
62
|
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
# @see {Google::Auth::AuthCallbackApp}
|
66
|
-
# @see {Google::Auth::ControllerHelpers}
|
63
|
+
# @see CallbackApp
|
67
64
|
# @note Requires sessions are enabled
|
68
65
|
class WebUserAuthorizer < Google::Auth::UserAuthorizer
|
69
66
|
STATE_PARAM = "state".freeze
|
@@ -261,7 +258,7 @@ module Google
|
|
261
258
|
# Google::Auth::WebUserAuthorizer::CallbackApp.call(env)
|
262
259
|
# end
|
263
260
|
#
|
264
|
-
# @see
|
261
|
+
# @see Google::Auth::WebUserAuthorizer
|
265
262
|
class CallbackApp
|
266
263
|
LOCATION_HEADER = "Location".freeze
|
267
264
|
REDIR_STATUS = 302
|
@@ -45,26 +45,37 @@ shared_examples "apply/apply! are OK" do
|
|
45
45
|
# auth client
|
46
46
|
describe "#fetch_access_token" do
|
47
47
|
let(:token) { "1/abcdef1234567890" }
|
48
|
-
let :
|
48
|
+
let :access_stub do
|
49
49
|
make_auth_stubs access_token: token
|
50
50
|
end
|
51
|
+
let :id_stub do
|
52
|
+
make_auth_stubs id_token: token
|
53
|
+
end
|
51
54
|
|
52
55
|
it "should set access_token to the fetched value" do
|
53
|
-
|
56
|
+
access_stub
|
54
57
|
@client.fetch_access_token!
|
55
58
|
expect(@client.access_token).to eq(token)
|
56
|
-
expect(
|
59
|
+
expect(access_stub).to have_been_requested
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should set id_token to the fetched value" do
|
63
|
+
skip unless @id_client
|
64
|
+
id_stub
|
65
|
+
@id_client.fetch_access_token!
|
66
|
+
expect(@id_client.id_token).to eq(token)
|
67
|
+
expect(id_stub).to have_been_requested
|
57
68
|
end
|
58
69
|
|
59
70
|
it "should notify refresh listeners after updating" do
|
60
|
-
|
71
|
+
access_stub
|
61
72
|
expect do |b|
|
62
73
|
@client.on_refresh(&b)
|
63
74
|
@client.fetch_access_token!
|
64
75
|
end.to yield_with_args(have_attributes(
|
65
76
|
access_token: "1/abcdef1234567890"
|
66
77
|
))
|
67
|
-
expect(
|
78
|
+
expect(access_stub).to have_been_requested
|
68
79
|
end
|
69
80
|
end
|
70
81
|
|
@@ -79,6 +90,18 @@ shared_examples "apply/apply! are OK" do
|
|
79
90
|
expect(md).to eq(want)
|
80
91
|
expect(stub).to have_been_requested
|
81
92
|
end
|
93
|
+
|
94
|
+
it "should update the target hash with fetched ID token" do
|
95
|
+
skip unless @id_client
|
96
|
+
token = "1/abcdef1234567890"
|
97
|
+
stub = make_auth_stubs id_token: token
|
98
|
+
|
99
|
+
md = { foo: "bar" }
|
100
|
+
@id_client.apply! md
|
101
|
+
want = { :foo => "bar", auth_key => "Bearer #{token}" }
|
102
|
+
expect(md).to eq(want)
|
103
|
+
expect(stub).to have_been_requested
|
104
|
+
end
|
82
105
|
end
|
83
106
|
|
84
107
|
describe "updater_proc" do
|
@@ -37,31 +37,52 @@ require "googleauth/compute_engine"
|
|
37
37
|
require "spec_helper"
|
38
38
|
|
39
39
|
describe Google::Auth::GCECredentials do
|
40
|
-
|
40
|
+
MD_ACCESS_URI = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
|
41
|
+
MD_ID_URI = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://pubsub.googleapis.com/&format=full".freeze
|
41
42
|
GCECredentials = Google::Auth::GCECredentials
|
42
43
|
|
43
44
|
before :example do
|
44
45
|
@client = GCECredentials.new
|
46
|
+
@id_client = GCECredentials.new target_audience: "https://pubsub.googleapis.com/"
|
45
47
|
end
|
46
48
|
|
47
|
-
def make_auth_stubs opts
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
49
|
+
def make_auth_stubs opts
|
50
|
+
if opts[:access_token]
|
51
|
+
body = MultiJson.dump("access_token" => opts[:access_token],
|
52
|
+
"token_type" => "Bearer",
|
53
|
+
"expires_in" => 3600)
|
54
|
+
|
55
|
+
uri = MD_ACCESS_URI
|
56
|
+
uri += "?scopes=#{Array(opts[:scope]).join ','}" if opts[:scope]
|
57
|
+
|
58
|
+
stub_request(:get, uri)
|
59
|
+
.with(headers: { "Metadata-Flavor" => "Google" })
|
60
|
+
.to_return(body: body,
|
61
|
+
status: 200,
|
62
|
+
headers: { "Content-Type" => "application/json" })
|
63
|
+
elsif opts[:id_token]
|
64
|
+
stub_request(:get, MD_ID_URI)
|
65
|
+
.with(headers: { "Metadata-Flavor" => "Google" })
|
66
|
+
.to_return(body: opts[:id_token],
|
67
|
+
status: 200,
|
68
|
+
headers: { "Content-Type" => "text/html" })
|
69
|
+
end
|
57
70
|
end
|
58
71
|
|
59
72
|
it_behaves_like "apply/apply! are OK"
|
60
73
|
|
61
74
|
context "metadata is unavailable" do
|
62
75
|
describe "#fetch_access_token" do
|
76
|
+
it "should pass scopes when requesting an access token" do
|
77
|
+
scopes = ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/bigtable.data"]
|
78
|
+
stub = make_auth_stubs access_token: "1/abcdef1234567890", scope: scopes
|
79
|
+
@client = GCECredentials.new(scope: scopes)
|
80
|
+
@client.fetch_access_token!
|
81
|
+
expect(stub).to have_been_requested
|
82
|
+
end
|
83
|
+
|
63
84
|
it "should fail if the metadata request returns a 404" do
|
64
|
-
stub = stub_request(:get,
|
85
|
+
stub = stub_request(:get, MD_ACCESS_URI)
|
65
86
|
.to_return(status: 404,
|
66
87
|
headers: { "Metadata-Flavor" => "Google" })
|
67
88
|
expect { @client.fetch_access_token! }
|
@@ -69,8 +90,26 @@ describe Google::Auth::GCECredentials do
|
|
69
90
|
expect(stub).to have_been_requested
|
70
91
|
end
|
71
92
|
|
93
|
+
it "should fail if the metadata request returns a 403" do
|
94
|
+
stub = stub_request(:get, MD_ACCESS_URI)
|
95
|
+
.to_return(status: 403,
|
96
|
+
headers: { "Metadata-Flavor" => "Google" })
|
97
|
+
expect { @client.fetch_access_token! }
|
98
|
+
.to raise_error Signet::AuthorizationError
|
99
|
+
expect(stub).to have_been_requested.times(6)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should fail if the metadata request returns a 500" do
|
103
|
+
stub = stub_request(:get, MD_ACCESS_URI)
|
104
|
+
.to_return(status: 500,
|
105
|
+
headers: { "Metadata-Flavor" => "Google" })
|
106
|
+
expect { @client.fetch_access_token! }
|
107
|
+
.to raise_error Signet::AuthorizationError
|
108
|
+
expect(stub).to have_been_requested.times(6)
|
109
|
+
end
|
110
|
+
|
72
111
|
it "should fail if the metadata request returns an unexpected code" do
|
73
|
-
stub = stub_request(:get,
|
112
|
+
stub = stub_request(:get, MD_ACCESS_URI)
|
74
113
|
.to_return(status: 503,
|
75
114
|
headers: { "Metadata-Flavor" => "Google" })
|
76
115
|
expect { @client.fetch_access_token! }
|
@@ -121,5 +160,19 @@ describe Google::Auth::GCECredentials do
|
|
121
160
|
expect(GCECredentials.on_gce?({}, true)).to eq(false)
|
122
161
|
expect(stub).to have_been_requested
|
123
162
|
end
|
163
|
+
|
164
|
+
it "should honor GCE_METADATA_HOST environment variable" do
|
165
|
+
ENV["GCE_METADATA_HOST"] = "mymetadata.example.com"
|
166
|
+
begin
|
167
|
+
stub = stub_request(:get, "http://mymetadata.example.com")
|
168
|
+
.with(headers: { "Metadata-Flavor" => "Google" })
|
169
|
+
.to_return(status: 200,
|
170
|
+
headers: { "Metadata-Flavor" => "Google" })
|
171
|
+
expect(GCECredentials.on_gce?({}, true)).to eq(true)
|
172
|
+
expect(stub).to have_been_requested
|
173
|
+
ensure
|
174
|
+
ENV.delete "GCE_METADATA_HOST"
|
175
|
+
end
|
176
|
+
end
|
124
177
|
end
|
125
178
|
end
|