googleauth 0.14.0 → 0.16.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/renovate.json +6 -0
- data/.github/sync-repo-settings.yaml +18 -0
- data/.github/workflows/ci.yml +55 -0
- data/.github/workflows/release-please.yml +39 -0
- data/.gitignore +3 -0
- data/.kokoro/populate-secrets.sh +76 -0
- data/.kokoro/release.cfg +7 -49
- data/.kokoro/release.sh +18 -0
- data/.kokoro/trampoline_v2.sh +489 -0
- data/.rubocop.yml +0 -2
- data/.toys/.toys.rb +45 -0
- data/.toys/ci.rb +43 -0
- data/.toys/kokoro/.toys.rb +66 -0
- data/.toys/kokoro/publish-docs.rb +67 -0
- data/.toys/kokoro/publish-gem.rb +53 -0
- data/.toys/linkinator.rb +43 -0
- data/.trampolinerc +48 -0
- data/CHANGELOG.md +69 -27
- data/Gemfile +2 -7
- data/README.md +9 -7
- data/googleauth.gemspec +2 -1
- data/lib/googleauth/compute_engine.rb +6 -5
- data/lib/googleauth/credentials.rb +167 -48
- data/lib/googleauth/credentials_loader.rb +1 -1
- data/lib/googleauth/iam.rb +1 -1
- data/lib/googleauth/id_tokens/key_sources.rb +7 -5
- data/lib/googleauth/id_tokens/verifier.rb +7 -9
- data/lib/googleauth/scope_util.rb +1 -1
- data/lib/googleauth/service_account.rb +35 -23
- data/lib/googleauth/signet.rb +1 -1
- data/lib/googleauth/stores/file_token_store.rb +1 -0
- data/lib/googleauth/stores/redis_token_store.rb +1 -0
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +4 -7
- data/spec/googleauth/compute_engine_spec.rb +18 -0
- data/spec/googleauth/credentials_spec.rb +228 -106
- data/spec/googleauth/service_account_spec.rb +8 -0
- metadata +18 -22
- data/.kokoro/build.bat +0 -16
- data/.kokoro/build.sh +0 -4
- data/.kokoro/continuous/common.cfg +0 -24
- data/.kokoro/continuous/linux.cfg +0 -25
- data/.kokoro/continuous/osx.cfg +0 -8
- data/.kokoro/continuous/post.cfg +0 -30
- data/.kokoro/continuous/windows.cfg +0 -29
- data/.kokoro/osx.sh +0 -4
- data/.kokoro/presubmit/common.cfg +0 -24
- data/.kokoro/presubmit/linux.cfg +0 -24
- data/.kokoro/presubmit/osx.cfg +0 -8
- data/.kokoro/presubmit/windows.cfg +0 -29
- data/.kokoro/trampoline.bat +0 -10
- data/.kokoro/trampoline.sh +0 -4
- data/Rakefile +0 -132
- data/rakelib/devsite_builder.rb +0 -45
- data/rakelib/link_checker.rb +0 -64
- data/rakelib/repo_metadata.rb +0 -59
@@ -103,7 +103,7 @@ module Google
|
|
103
103
|
return make_creds options.merge(json_key_io: f)
|
104
104
|
end
|
105
105
|
elsif service_account_env_vars? || authorized_user_env_vars?
|
106
|
-
|
106
|
+
make_creds options
|
107
107
|
end
|
108
108
|
rescue StandardError => e
|
109
109
|
raise "#{NOT_FOUND_ERROR}: #{e}"
|
data/lib/googleauth/iam.rb
CHANGED
@@ -171,7 +171,9 @@ module Google
|
|
171
171
|
curve_name = CURVE_NAME_MAP[jwk[:crv]]
|
172
172
|
raise KeySourceError, "Unsupported EC curve #{jwk[:crv]}" unless curve_name
|
173
173
|
group = OpenSSL::PKey::EC::Group.new curve_name
|
174
|
-
|
174
|
+
x_hex = x_data.unpack1 "H*"
|
175
|
+
y_hex = y_data.unpack1 "H*"
|
176
|
+
bn = OpenSSL::BN.new ["04#{x_hex}#{y_hex}"].pack("H*"), 2
|
175
177
|
key = OpenSSL::PKey::EC.new curve_name
|
176
178
|
key.public_key = OpenSSL::PKey::EC::Point.new group, bn
|
177
179
|
key
|
@@ -284,10 +286,10 @@ module Google
|
|
284
286
|
raise KeySourceError, "Unable to retrieve data from #{uri}" unless response.is_a? Net::HTTPSuccess
|
285
287
|
|
286
288
|
data = begin
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
289
|
+
JSON.parse response.body
|
290
|
+
rescue JSON::ParserError
|
291
|
+
raise KeySourceError, "Unable to parse JSON"
|
292
|
+
end
|
291
293
|
|
292
294
|
@current_keys = Array(interpret_json(data))
|
293
295
|
end
|
@@ -105,15 +105,13 @@ module Google
|
|
105
105
|
def decode_token token, keys, aud, azp, iss
|
106
106
|
payload = nil
|
107
107
|
keys.find do |key|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
nil # Try the next key
|
116
|
-
end
|
108
|
+
options = { algorithms: key.algorithm }
|
109
|
+
decoded_token = JWT.decode token, key.key, true, options
|
110
|
+
payload = decoded_token.first
|
111
|
+
rescue JWT::ExpiredSignature
|
112
|
+
raise ExpiredTokenError, "Token signature is expired"
|
113
|
+
rescue JWT::DecodeError
|
114
|
+
nil # Try the next key
|
117
115
|
end
|
118
116
|
|
119
117
|
normalize_and_verify_payload payload, aud, azp, iss
|
@@ -53,12 +53,18 @@ module Google
|
|
53
53
|
attr_reader :project_id
|
54
54
|
attr_reader :quota_project_id
|
55
55
|
|
56
|
+
def enable_self_signed_jwt?
|
57
|
+
@enable_self_signed_jwt
|
58
|
+
end
|
59
|
+
|
56
60
|
# Creates a ServiceAccountCredentials.
|
57
61
|
#
|
58
62
|
# @param json_key_io [IO] an IO from which the JSON key can be read
|
59
63
|
# @param scope [string|array|nil] the scope(s) to access
|
60
64
|
def self.make_creds options = {}
|
61
|
-
json_key_io, scope, target_audience
|
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
|
62
68
|
raise ArgumentError, "Cannot specify both scope and target_audience" if scope && target_audience
|
63
69
|
|
64
70
|
if json_key_io
|
@@ -71,14 +77,15 @@ module Google
|
|
71
77
|
end
|
72
78
|
project_id ||= CredentialsLoader.load_gcloud_project_id
|
73
79
|
|
74
|
-
new(token_credential_uri: TOKEN_CRED_URI,
|
75
|
-
audience:
|
76
|
-
scope:
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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)
|
82
89
|
.configure_connection(options)
|
83
90
|
end
|
84
91
|
|
@@ -94,30 +101,35 @@ module Google
|
|
94
101
|
def initialize options = {}
|
95
102
|
@project_id = options[:project_id]
|
96
103
|
@quota_project_id = options[:quota_project_id]
|
104
|
+
@enable_self_signed_jwt = options[:enable_self_signed_jwt] ? true : false
|
97
105
|
super options
|
98
106
|
end
|
99
107
|
|
100
|
-
# Extends the base class
|
101
|
-
#
|
102
|
-
# If scope(s) is not set, it creates a transient
|
103
|
-
# ServiceAccountJwtHeaderCredentials instance and uses that to
|
104
|
-
# authenticate instead.
|
108
|
+
# Extends the base class to use a transient
|
109
|
+
# ServiceAccountJwtHeaderCredentials for certain cases.
|
105
110
|
def apply! a_hash, opts = {}
|
106
|
-
# Use
|
107
|
-
|
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
|
108
117
|
super
|
109
|
-
return
|
110
118
|
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
111
122
|
|
123
|
+
def apply_self_signed_jwt! a_hash
|
112
124
|
# Use the ServiceAccountJwtHeaderCredentials using the same cred values
|
113
|
-
# if no scopes are set.
|
114
125
|
cred_json = {
|
115
|
-
private_key:
|
116
|
-
client_email: @issuer
|
126
|
+
private_key: @signing_key.to_s,
|
127
|
+
client_email: @issuer,
|
128
|
+
project_id: @project_id,
|
129
|
+
quota_project_id: @quota_project_id
|
117
130
|
}
|
118
|
-
alt_clz = ServiceAccountJwtHeaderCredentials
|
119
131
|
key_io = StringIO.new MultiJson.dump(cred_json)
|
120
|
-
alt =
|
132
|
+
alt = ServiceAccountJwtHeaderCredentials.make_creds json_key_io: key_io
|
121
133
|
alt.apply! a_hash
|
122
134
|
end
|
123
135
|
end
|
@@ -193,7 +205,7 @@ module Google
|
|
193
205
|
# Returns a reference to the #apply method, suitable for passing as
|
194
206
|
# a closure
|
195
207
|
def updater_proc
|
196
|
-
|
208
|
+
proc { |a_hash, opts = {}| apply a_hash, opts }
|
197
209
|
end
|
198
210
|
|
199
211
|
protected
|
data/lib/googleauth/signet.rb
CHANGED
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
|
@@ -192,7 +189,7 @@ module Google
|
|
192
189
|
# May raise an error if an authorization code is present in the session
|
193
190
|
# and exchange of the code fails
|
194
191
|
def get_credentials user_id, request = nil, scope = nil
|
195
|
-
if request
|
192
|
+
if request&.session&.key? CALLBACK_STATE_KEY
|
196
193
|
# Note - in theory, no need to check required scope as this is
|
197
194
|
# expected to be called immediately after a return from authorization
|
198
195
|
state_json = request.session.delete CALLBACK_STATE_KEY
|
@@ -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
|
@@ -90,6 +90,24 @@ describe Google::Auth::GCECredentials do
|
|
90
90
|
expect(stub).to have_been_requested
|
91
91
|
end
|
92
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
|
+
|
93
111
|
it "should fail if the metadata request returns an unexpected code" do
|
94
112
|
stub = stub_request(:get, MD_ACCESS_URI)
|
95
113
|
.to_return(status: 503,
|
@@ -46,42 +46,47 @@ describe Google::Auth::Credentials, :private do
|
|
46
46
|
}
|
47
47
|
end
|
48
48
|
|
49
|
-
|
49
|
+
def mock_signet
|
50
50
|
mocked_signet = double "Signet::OAuth2::Client"
|
51
51
|
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
|
52
52
|
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
|
53
53
|
allow(mocked_signet).to receive(:client_id)
|
54
54
|
allow(Signet::OAuth2::Client).to receive(:new) do |options|
|
55
|
+
yield options if block_given?
|
56
|
+
mocked_signet
|
57
|
+
end
|
58
|
+
mocked_signet
|
59
|
+
end
|
60
|
+
|
61
|
+
it "uses a default scope" do
|
62
|
+
mock_signet do |options|
|
55
63
|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
|
56
64
|
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
|
57
65
|
expect(options[:scope]).to eq([])
|
58
66
|
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
|
59
67
|
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
|
60
|
-
|
61
|
-
mocked_signet
|
62
68
|
end
|
63
69
|
|
64
70
|
Google::Auth::Credentials.new default_keyfile_hash
|
65
71
|
end
|
66
72
|
|
67
73
|
it "uses a custom scope" do
|
68
|
-
|
69
|
-
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
|
70
|
-
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
|
71
|
-
allow(mocked_signet).to receive(:client_id)
|
72
|
-
allow(Signet::OAuth2::Client).to receive(:new) do |options|
|
74
|
+
mock_signet do |options|
|
73
75
|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
|
74
76
|
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
|
75
77
|
expect(options[:scope]).to eq(["http://example.com/scope"])
|
76
78
|
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
|
77
79
|
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
|
78
|
-
|
79
|
-
mocked_signet
|
80
80
|
end
|
81
81
|
|
82
82
|
Google::Auth::Credentials.new default_keyfile_hash, scope: "http://example.com/scope"
|
83
83
|
end
|
84
84
|
|
85
|
+
it "uses empty paths and env_vars by default" do
|
86
|
+
expect(Google::Auth::Credentials.paths).to eq([])
|
87
|
+
expect(Google::Auth::Credentials.env_vars).to eq([])
|
88
|
+
end
|
89
|
+
|
85
90
|
describe "using CONSTANTS" do
|
86
91
|
it "can be subclassed to pass in other env paths" do
|
87
92
|
test_path_env_val = "/unknown/path/to/file.txt".freeze
|
@@ -101,21 +106,22 @@ describe Google::Auth::Credentials, :private do
|
|
101
106
|
allow(::File).to receive(:file?).with(test_path_env_val) { false }
|
102
107
|
allow(::File).to receive(:file?).with(test_json_env_val) { false }
|
103
108
|
|
104
|
-
mocked_signet =
|
105
|
-
|
106
|
-
allow(
|
107
|
-
allow(mocked_signet).to receive(:client_id)
|
108
|
-
allow(Signet::OAuth2::Client).to receive(:new) do |options|
|
109
|
+
mocked_signet = mock_signet
|
110
|
+
|
111
|
+
allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
|
109
112
|
expect(options[:token_credential_uri]).to eq("https://example.com/token")
|
110
113
|
expect(options[:audience]).to eq("https://example.com/audience")
|
111
114
|
expect(options[:scope]).to eq(["http://example.com/scope"])
|
112
|
-
expect(options[:
|
113
|
-
expect(options[:
|
115
|
+
expect(options[:enable_self_signed_jwt]).to eq(true)
|
116
|
+
expect(options[:target_audience]).to be_nil
|
117
|
+
expect(options[:json_key_io].read).to eq(test_json_env_val)
|
114
118
|
|
115
|
-
|
119
|
+
# This should really be a Signet::OAuth2::Client object,
|
120
|
+
# but mocking is making that difficult, so return a valid hash instead.
|
121
|
+
default_keyfile_hash
|
116
122
|
end
|
117
123
|
|
118
|
-
creds = TestCredentials1.default
|
124
|
+
creds = TestCredentials1.default enable_self_signed_jwt: true
|
119
125
|
expect(creds).to be_a_kind_of(TestCredentials1)
|
120
126
|
expect(creds.client).to eq(mocked_signet)
|
121
127
|
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
|
@@ -130,25 +136,28 @@ describe Google::Auth::Credentials, :private do
|
|
130
136
|
DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
|
131
137
|
end
|
132
138
|
|
139
|
+
json_content = JSON.generate default_keyfile_hash
|
140
|
+
|
133
141
|
allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
|
134
142
|
allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
|
135
143
|
allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
|
136
144
|
allow(::ENV).to receive(:[]).with("PATH_ENV_TEST") { "/unknown/path/to/file.txt" }
|
137
145
|
allow(::File).to receive(:file?).with("/unknown/path/to/file.txt") { true }
|
138
|
-
allow(::File).to receive(:read).with("/unknown/path/to/file.txt") {
|
146
|
+
allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { json_content }
|
139
147
|
|
140
|
-
mocked_signet =
|
141
|
-
|
142
|
-
allow(
|
143
|
-
allow(mocked_signet).to receive(:client_id)
|
144
|
-
allow(Signet::OAuth2::Client).to receive(:new) do |options|
|
148
|
+
mocked_signet = mock_signet
|
149
|
+
|
150
|
+
allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
|
145
151
|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
|
146
152
|
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
|
147
153
|
expect(options[:scope]).to eq(["http://example.com/scope"])
|
148
|
-
expect(options[:
|
149
|
-
expect(options[:
|
154
|
+
expect(options[:enable_self_signed_jwt]).to be_nil
|
155
|
+
expect(options[:target_audience]).to be_nil
|
156
|
+
expect(options[:json_key_io].read).to eq(json_content)
|
150
157
|
|
151
|
-
|
158
|
+
# This should really be a Signet::OAuth2::Client object,
|
159
|
+
# but mocking is making that difficult, so return a valid hash instead.
|
160
|
+
default_keyfile_hash
|
152
161
|
end
|
153
162
|
|
154
163
|
creds = TestCredentials2.default
|
@@ -175,18 +184,19 @@ describe Google::Auth::Credentials, :private do
|
|
175
184
|
allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
|
176
185
|
allow(::ENV).to receive(:[]).with("JSON_ENV_TEST") { test_json_env_val }
|
177
186
|
|
178
|
-
mocked_signet =
|
179
|
-
|
180
|
-
allow(
|
181
|
-
allow(mocked_signet).to receive(:client_id)
|
182
|
-
allow(Signet::OAuth2::Client).to receive(:new) do |options|
|
187
|
+
mocked_signet = mock_signet
|
188
|
+
|
189
|
+
allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
|
183
190
|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
|
184
191
|
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
|
185
192
|
expect(options[:scope]).to eq(["http://example.com/scope"])
|
186
|
-
expect(options[:
|
187
|
-
expect(options[:
|
193
|
+
expect(options[:enable_self_signed_jwt]).to be_nil
|
194
|
+
expect(options[:target_audience]).to be_nil
|
195
|
+
expect(options[:json_key_io].read).to eq(test_json_env_val)
|
188
196
|
|
189
|
-
|
197
|
+
# This should really be a Signet::OAuth2::Client object,
|
198
|
+
# but mocking is making that difficult, so return a valid hash instead.
|
199
|
+
default_keyfile_hash
|
190
200
|
end
|
191
201
|
|
192
202
|
creds = TestCredentials3.default
|
@@ -204,25 +214,28 @@ describe Google::Auth::Credentials, :private do
|
|
204
214
|
DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
|
205
215
|
end
|
206
216
|
|
217
|
+
json_content = JSON.generate default_keyfile_hash
|
218
|
+
|
207
219
|
allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
|
208
220
|
allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
|
209
221
|
allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
|
210
222
|
allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
|
211
223
|
allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { true }
|
212
|
-
allow(::File).to receive(:read).with("~/default/path/to/file.txt") {
|
224
|
+
allow(::File).to receive(:read).with("~/default/path/to/file.txt") { json_content }
|
225
|
+
|
226
|
+
mocked_signet = mock_signet
|
213
227
|
|
214
|
-
|
215
|
-
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
|
216
|
-
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
|
217
|
-
allow(mocked_signet).to receive(:client_id)
|
218
|
-
allow(Signet::OAuth2::Client).to receive(:new) do |options|
|
228
|
+
allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
|
219
229
|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
|
220
230
|
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
|
221
231
|
expect(options[:scope]).to eq(["http://example.com/scope"])
|
222
|
-
expect(options[:
|
223
|
-
expect(options[:
|
232
|
+
expect(options[:enable_self_signed_jwt]).to be_nil
|
233
|
+
expect(options[:target_audience]).to be_nil
|
234
|
+
expect(options[:json_key_io].read).to eq(json_content)
|
224
235
|
|
225
|
-
|
236
|
+
# This should really be a Signet::OAuth2::Client object,
|
237
|
+
# but mocking is making that difficult, so return a valid hash instead.
|
238
|
+
default_keyfile_hash
|
226
239
|
end
|
227
240
|
|
228
241
|
creds = TestCredentials4.default
|
@@ -246,26 +259,18 @@ describe Google::Auth::Credentials, :private do
|
|
246
259
|
allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
|
247
260
|
allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
|
248
261
|
|
249
|
-
mocked_signet =
|
250
|
-
|
251
|
-
allow(
|
252
|
-
allow(mocked_signet).to receive(:client_id)
|
253
|
-
allow(Google::Auth).to receive(:get_application_default) do |scope|
|
262
|
+
mocked_signet = mock_signet
|
263
|
+
|
264
|
+
allow(Google::Auth).to receive(:get_application_default) do |scope, options|
|
254
265
|
expect(scope).to eq([TestCredentials5::SCOPE])
|
266
|
+
expect(options[:enable_self_signed_jwt]).to be_nil
|
267
|
+
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
|
268
|
+
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
|
255
269
|
|
256
270
|
# This should really be a Signet::OAuth2::Client object,
|
257
271
|
# but mocking is making that difficult, so return a valid hash instead.
|
258
272
|
default_keyfile_hash
|
259
273
|
end
|
260
|
-
allow(Signet::OAuth2::Client).to receive(:new) do |options|
|
261
|
-
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
|
262
|
-
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
|
263
|
-
expect(options[:scope]).to eq(["http://example.com/scope"])
|
264
|
-
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
|
265
|
-
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
|
266
|
-
|
267
|
-
mocked_signet
|
268
|
-
end
|
269
274
|
|
270
275
|
creds = TestCredentials5.default
|
271
276
|
expect(creds).to be_a_kind_of(TestCredentials5)
|
@@ -273,6 +278,31 @@ describe Google::Auth::Credentials, :private do
|
|
273
278
|
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
|
274
279
|
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
|
275
280
|
end
|
281
|
+
|
282
|
+
it "can be subclassed to pass in other env paths" do
|
283
|
+
class TestCredentials6 < Google::Auth::Credentials
|
284
|
+
TOKEN_CREDENTIAL_URI = "https://example.com/token".freeze
|
285
|
+
AUDIENCE = "https://example.com/audience".freeze
|
286
|
+
SCOPE = "http://example.com/scope".freeze
|
287
|
+
PATH_ENV_VARS = ["TEST_PATH"].freeze
|
288
|
+
JSON_ENV_VARS = ["TEST_JSON_VARS"].freeze
|
289
|
+
DEFAULT_PATHS = ["~/default/path/to/file.txt"]
|
290
|
+
end
|
291
|
+
|
292
|
+
class TestCredentials7 < TestCredentials6
|
293
|
+
end
|
294
|
+
|
295
|
+
expect(TestCredentials7.token_credential_uri).to eq("https://example.com/token")
|
296
|
+
expect(TestCredentials7.audience).to eq("https://example.com/audience")
|
297
|
+
expect(TestCredentials7.scope).to eq(["http://example.com/scope"])
|
298
|
+
expect(TestCredentials7.env_vars).to eq(["TEST_PATH", "TEST_JSON_VARS"])
|
299
|
+
expect(TestCredentials7.paths).to eq(["~/default/path/to/file.txt"])
|
300
|
+
|
301
|
+
TestCredentials7::TOKEN_CREDENTIAL_URI = "https://example.com/token2"
|
302
|
+
expect(TestCredentials7.token_credential_uri).to eq("https://example.com/token2")
|
303
|
+
TestCredentials7::AUDIENCE = nil
|
304
|
+
expect(TestCredentials7.audience).to eq("https://example.com/audience")
|
305
|
+
end
|
276
306
|
end
|
277
307
|
|
278
308
|
describe "using class methods" do
|
@@ -293,18 +323,19 @@ describe Google::Auth::Credentials, :private do
|
|
293
323
|
allow(::File).to receive(:file?).with(test_path_env_val) { false }
|
294
324
|
allow(::File).to receive(:file?).with(test_json_env_val) { false }
|
295
325
|
|
296
|
-
mocked_signet =
|
297
|
-
|
298
|
-
allow(
|
299
|
-
allow(mocked_signet).to receive(:client_id)
|
300
|
-
allow(Signet::OAuth2::Client).to receive(:new) do |options|
|
326
|
+
mocked_signet = mock_signet
|
327
|
+
|
328
|
+
allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
|
301
329
|
expect(options[:token_credential_uri]).to eq("https://example.com/token")
|
302
330
|
expect(options[:audience]).to eq("https://example.com/audience")
|
303
331
|
expect(options[:scope]).to eq(["http://example.com/scope"])
|
304
|
-
expect(options[:
|
305
|
-
expect(options[:
|
332
|
+
expect(options[:enable_self_signed_jwt]).to be_nil
|
333
|
+
expect(options[:target_audience]).to be_nil
|
334
|
+
expect(options[:json_key_io].read).to eq(test_json_env_val)
|
306
335
|
|
307
|
-
|
336
|
+
# This should really be a Signet::OAuth2::Client object,
|
337
|
+
# but mocking is making that difficult, so return a valid hash instead.
|
338
|
+
default_keyfile_hash
|
308
339
|
end
|
309
340
|
|
310
341
|
creds = TestCredentials11.default
|
@@ -321,25 +352,28 @@ describe Google::Auth::Credentials, :private do
|
|
321
352
|
self.paths = ["~/default/path/to/file.txt"]
|
322
353
|
end
|
323
354
|
|
355
|
+
json_content = JSON.generate default_keyfile_hash
|
356
|
+
|
324
357
|
allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
|
325
358
|
allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
|
326
359
|
allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
|
327
360
|
allow(::ENV).to receive(:[]).with("PATH_ENV_TEST") { "/unknown/path/to/file.txt" }
|
328
361
|
allow(::File).to receive(:file?).with("/unknown/path/to/file.txt") { true }
|
329
|
-
allow(::File).to receive(:read).with("/unknown/path/to/file.txt") {
|
362
|
+
allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { json_content }
|
363
|
+
|
364
|
+
mocked_signet = mock_signet
|
330
365
|
|
331
|
-
|
332
|
-
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
|
333
|
-
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
|
334
|
-
allow(mocked_signet).to receive(:client_id)
|
335
|
-
allow(Signet::OAuth2::Client).to receive(:new) do |options|
|
366
|
+
allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
|
336
367
|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
|
337
368
|
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
|
338
369
|
expect(options[:scope]).to eq(["http://example.com/scope"])
|
339
|
-
expect(options[:
|
340
|
-
expect(options[:
|
370
|
+
expect(options[:enable_self_signed_jwt]).to be_nil
|
371
|
+
expect(options[:target_audience]).to be_nil
|
372
|
+
expect(options[:json_key_io].read).to eq(json_content)
|
341
373
|
|
342
|
-
|
374
|
+
# This should really be a Signet::OAuth2::Client object,
|
375
|
+
# but mocking is making that difficult, so return a valid hash instead.
|
376
|
+
default_keyfile_hash
|
343
377
|
end
|
344
378
|
|
345
379
|
creds = TestCredentials12.default
|
@@ -365,18 +399,19 @@ describe Google::Auth::Credentials, :private do
|
|
365
399
|
allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
|
366
400
|
allow(::ENV).to receive(:[]).with("JSON_ENV_TEST") { test_json_env_val }
|
367
401
|
|
368
|
-
mocked_signet =
|
369
|
-
|
370
|
-
allow(
|
371
|
-
allow(mocked_signet).to receive(:client_id)
|
372
|
-
allow(Signet::OAuth2::Client).to receive(:new) do |options|
|
402
|
+
mocked_signet = mock_signet
|
403
|
+
|
404
|
+
allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
|
373
405
|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
|
374
406
|
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
|
375
407
|
expect(options[:scope]).to eq(["http://example.com/scope"])
|
376
|
-
expect(options[:
|
377
|
-
expect(options[:
|
408
|
+
expect(options[:enable_self_signed_jwt]).to be_nil
|
409
|
+
expect(options[:target_audience]).to be_nil
|
410
|
+
expect(options[:json_key_io].read).to eq(test_json_env_val)
|
378
411
|
|
379
|
-
|
412
|
+
# This should really be a Signet::OAuth2::Client object,
|
413
|
+
# but mocking is making that difficult, so return a valid hash instead.
|
414
|
+
default_keyfile_hash
|
380
415
|
end
|
381
416
|
|
382
417
|
creds = TestCredentials13.default
|
@@ -393,25 +428,28 @@ describe Google::Auth::Credentials, :private do
|
|
393
428
|
self.paths = ["~/default/path/to/file.txt"]
|
394
429
|
end
|
395
430
|
|
431
|
+
json_content = JSON.generate default_keyfile_hash
|
432
|
+
|
396
433
|
allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
|
397
434
|
allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
|
398
435
|
allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
|
399
436
|
allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
|
400
437
|
allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { true }
|
401
|
-
allow(::File).to receive(:read).with("~/default/path/to/file.txt") {
|
438
|
+
allow(::File).to receive(:read).with("~/default/path/to/file.txt") { json_content }
|
439
|
+
|
440
|
+
mocked_signet = mock_signet
|
402
441
|
|
403
|
-
|
404
|
-
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
|
405
|
-
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
|
406
|
-
allow(mocked_signet).to receive(:client_id)
|
407
|
-
allow(Signet::OAuth2::Client).to receive(:new) do |options|
|
442
|
+
allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
|
408
443
|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
|
409
444
|
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
|
410
445
|
expect(options[:scope]).to eq(["http://example.com/scope"])
|
411
|
-
expect(options[:
|
412
|
-
expect(options[:
|
446
|
+
expect(options[:enable_self_signed_jwt]).to be_nil
|
447
|
+
expect(options[:target_audience]).to be_nil
|
448
|
+
expect(options[:json_key_io].read).to eq(json_content)
|
413
449
|
|
414
|
-
|
450
|
+
# This should really be a Signet::OAuth2::Client object,
|
451
|
+
# but mocking is making that difficult, so return a valid hash instead.
|
452
|
+
default_keyfile_hash
|
415
453
|
end
|
416
454
|
|
417
455
|
creds = TestCredentials14.default
|
@@ -421,7 +459,7 @@ describe Google::Auth::Credentials, :private do
|
|
421
459
|
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
|
422
460
|
end
|
423
461
|
|
424
|
-
it "subclasses that find no matches default to Google::Auth.get_application_default" do
|
462
|
+
it "subclasses that find no matches default to Google::Auth.get_application_default with self-signed jwt enabled" do
|
425
463
|
class TestCredentials15 < Google::Auth::Credentials
|
426
464
|
self.scope = "http://example.com/scope"
|
427
465
|
self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY]
|
@@ -434,33 +472,117 @@ describe Google::Auth::Credentials, :private do
|
|
434
472
|
allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
|
435
473
|
allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
|
436
474
|
|
437
|
-
mocked_signet =
|
438
|
-
|
439
|
-
allow(
|
440
|
-
allow(mocked_signet).to receive(:client_id)
|
441
|
-
allow(Google::Auth).to receive(:get_application_default) do |scope|
|
475
|
+
mocked_signet = mock_signet
|
476
|
+
|
477
|
+
allow(Google::Auth).to receive(:get_application_default) do |scope, options|
|
442
478
|
expect(scope).to eq(TestCredentials15.scope)
|
479
|
+
expect(options[:enable_self_signed_jwt]).to eq(true)
|
480
|
+
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
|
481
|
+
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
|
443
482
|
|
444
483
|
# This should really be a Signet::OAuth2::Client object,
|
445
484
|
# but mocking is making that difficult, so return a valid hash instead.
|
446
485
|
default_keyfile_hash
|
447
486
|
end
|
448
|
-
|
487
|
+
|
488
|
+
creds = TestCredentials15.default enable_self_signed_jwt: true
|
489
|
+
expect(creds).to be_a_kind_of(TestCredentials15)
|
490
|
+
expect(creds.client).to eq(mocked_signet)
|
491
|
+
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
|
492
|
+
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
|
493
|
+
end
|
494
|
+
|
495
|
+
it "subclasses that find no matches default to Google::Auth.get_application_default with self-signed jwt disabled" do
|
496
|
+
class TestCredentials16 < Google::Auth::Credentials
|
497
|
+
self.scope = "http://example.com/scope"
|
498
|
+
self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY]
|
499
|
+
self.paths = ["~/default/path/to/file.txt"]
|
500
|
+
end
|
501
|
+
|
502
|
+
allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
|
503
|
+
allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
|
504
|
+
allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
|
505
|
+
allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
|
506
|
+
allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
|
507
|
+
|
508
|
+
mocked_signet = mock_signet
|
509
|
+
|
510
|
+
allow(Google::Auth).to receive(:get_application_default) do |scope, options|
|
511
|
+
expect(scope).to eq(TestCredentials16.scope)
|
512
|
+
expect(options[:enable_self_signed_jwt]).to be_nil
|
449
513
|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
|
450
514
|
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
|
451
|
-
expect(options[:scope]).to eq(["http://example.com/scope"])
|
452
|
-
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
|
453
|
-
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
|
454
515
|
|
455
|
-
|
516
|
+
# This should really be a Signet::OAuth2::Client object,
|
517
|
+
# but mocking is making that difficult, so return a valid hash instead.
|
518
|
+
default_keyfile_hash
|
456
519
|
end
|
457
520
|
|
458
|
-
creds =
|
459
|
-
expect(creds).to be_a_kind_of(
|
521
|
+
creds = TestCredentials16.default
|
522
|
+
expect(creds).to be_a_kind_of(TestCredentials16)
|
460
523
|
expect(creds.client).to eq(mocked_signet)
|
461
524
|
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
|
462
525
|
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
|
463
526
|
end
|
527
|
+
|
528
|
+
it "subclasses that find no matches default to Google::Auth.get_application_default with custom values" do
|
529
|
+
scope2 = "http://example.com/scope2"
|
530
|
+
|
531
|
+
class TestCredentials17 < Google::Auth::Credentials
|
532
|
+
self.scope = "http://example.com/scope"
|
533
|
+
self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY]
|
534
|
+
self.paths = ["~/default/path/to/file.txt"]
|
535
|
+
self.token_credential_uri = "https://example.com/token2"
|
536
|
+
self.audience = "https://example.com/token3"
|
537
|
+
end
|
538
|
+
|
539
|
+
allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
|
540
|
+
allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
|
541
|
+
allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
|
542
|
+
allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
|
543
|
+
allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
|
544
|
+
|
545
|
+
mocked_signet = mock_signet
|
546
|
+
|
547
|
+
allow(Google::Auth).to receive(:get_application_default) do |scope, options|
|
548
|
+
expect(scope).to eq(scope2)
|
549
|
+
expect(options[:enable_self_signed_jwt]).to eq(false)
|
550
|
+
expect(options[:token_credential_uri]).to eq("https://example.com/token2")
|
551
|
+
expect(options[:audience]).to eq("https://example.com/token3")
|
552
|
+
|
553
|
+
# This should really be a Signet::OAuth2::Client object,
|
554
|
+
# but mocking is making that difficult, so return a valid hash instead.
|
555
|
+
default_keyfile_hash
|
556
|
+
end
|
557
|
+
|
558
|
+
creds = TestCredentials17.default scope: scope2, enable_self_signed_jwt: true
|
559
|
+
expect(creds).to be_a_kind_of(TestCredentials17)
|
560
|
+
expect(creds.client).to eq(mocked_signet)
|
561
|
+
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
|
562
|
+
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
|
563
|
+
end
|
564
|
+
|
565
|
+
it "subclasses delegate up the class hierarchy" do
|
566
|
+
class TestCredentials18 < Google::Auth::Credentials
|
567
|
+
self.scope = "http://example.com/scope"
|
568
|
+
self.target_audience = "https://example.com/target_audience"
|
569
|
+
self.env_vars = ["TEST_PATH", "TEST_JSON_VARS"]
|
570
|
+
self.paths = ["~/default/path/to/file.txt"]
|
571
|
+
end
|
572
|
+
|
573
|
+
class TestCredentials19 < TestCredentials18
|
574
|
+
end
|
575
|
+
|
576
|
+
expect(TestCredentials19.scope).to eq(["http://example.com/scope"])
|
577
|
+
expect(TestCredentials19.target_audience).to eq("https://example.com/target_audience")
|
578
|
+
expect(TestCredentials19.env_vars).to eq(["TEST_PATH", "TEST_JSON_VARS"])
|
579
|
+
expect(TestCredentials19.paths).to eq(["~/default/path/to/file.txt"])
|
580
|
+
|
581
|
+
TestCredentials19.token_credential_uri = "https://example.com/token2"
|
582
|
+
expect(TestCredentials19.token_credential_uri).to eq("https://example.com/token2")
|
583
|
+
TestCredentials19.token_credential_uri = nil
|
584
|
+
expect(TestCredentials19.token_credential_uri).to eq("https://oauth2.googleapis.com/token")
|
585
|
+
end
|
464
586
|
end
|
465
587
|
|
466
588
|
it "warns when cloud sdk credentials are used" do
|