googleauth 0.5.1 → 0.14.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 +5 -5
- data/.github/CODEOWNERS +7 -0
- data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +5 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +36 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
- data/.github/ISSUE_TEMPLATE/support_request.md +7 -0
- data/.kokoro/build.bat +16 -0
- data/.kokoro/build.sh +4 -0
- data/.kokoro/continuous/common.cfg +24 -0
- data/.kokoro/continuous/linux.cfg +25 -0
- data/.kokoro/continuous/osx.cfg +8 -0
- data/.kokoro/continuous/post.cfg +30 -0
- data/.kokoro/continuous/windows.cfg +29 -0
- data/.kokoro/osx.sh +4 -0
- data/.kokoro/presubmit/common.cfg +24 -0
- data/.kokoro/presubmit/linux.cfg +24 -0
- data/.kokoro/presubmit/osx.cfg +8 -0
- data/.kokoro/presubmit/windows.cfg +29 -0
- data/.kokoro/release.cfg +94 -0
- data/.kokoro/trampoline.bat +10 -0
- data/.kokoro/trampoline.sh +4 -0
- data/.repo-metadata.json +5 -0
- data/.rubocop.yml +19 -1
- data/CHANGELOG.md +112 -19
- data/CODE_OF_CONDUCT.md +43 -0
- data/Gemfile +19 -13
- data/{COPYING → LICENSE} +0 -0
- data/README.md +58 -18
- data/Rakefile +126 -9
- data/googleauth.gemspec +28 -25
- data/integration/helper.rb +31 -0
- data/integration/id_tokens/key_source_test.rb +74 -0
- data/lib/googleauth.rb +7 -96
- data/lib/googleauth/application_default.rb +81 -0
- data/lib/googleauth/client_id.rb +21 -19
- data/lib/googleauth/compute_engine.rb +70 -43
- data/lib/googleauth/credentials.rb +442 -0
- data/lib/googleauth/credentials_loader.rb +117 -43
- data/lib/googleauth/default_credentials.rb +93 -0
- data/lib/googleauth/iam.rb +11 -11
- 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 +50 -0
- data/lib/googleauth/scope_util.rb +12 -12
- data/lib/googleauth/service_account.rb +74 -63
- data/lib/googleauth/signet.rb +55 -13
- data/lib/googleauth/stores/file_token_store.rb +8 -8
- data/lib/googleauth/stores/redis_token_store.rb +22 -22
- data/lib/googleauth/token_store.rb +6 -6
- data/lib/googleauth/user_authorizer.rb +80 -68
- data/lib/googleauth/user_refresh.rb +44 -35
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +77 -68
- data/rakelib/devsite_builder.rb +45 -0
- data/rakelib/link_checker.rb +64 -0
- data/rakelib/repo_metadata.rb +59 -0
- data/spec/googleauth/apply_auth_examples.rb +74 -50
- data/spec/googleauth/client_id_spec.rb +75 -55
- data/spec/googleauth/compute_engine_spec.rb +98 -46
- data/spec/googleauth/credentials_spec.rb +478 -0
- data/spec/googleauth/get_application_default_spec.rb +149 -111
- data/spec/googleauth/iam_spec.rb +25 -25
- data/spec/googleauth/scope_util_spec.rb +26 -24
- data/spec/googleauth/service_account_spec.rb +269 -144
- data/spec/googleauth/signet_spec.rb +101 -30
- data/spec/googleauth/stores/file_token_store_spec.rb +12 -13
- data/spec/googleauth/stores/redis_token_store_spec.rb +11 -11
- data/spec/googleauth/stores/store_examples.rb +16 -16
- data/spec/googleauth/user_authorizer_spec.rb +153 -124
- data/spec/googleauth/user_refresh_spec.rb +186 -121
- data/spec/googleauth/web_user_authorizer_spec.rb +82 -69
- data/spec/spec_helper.rb +21 -19
- 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 +87 -34
- data/.rubocop_todo.yml +0 -32
- data/.travis.yml +0 -37
@@ -0,0 +1,442 @@
|
|
1
|
+
# Copyright 2017, Google Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
#
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are
|
6
|
+
# met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright
|
9
|
+
# notice, this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above
|
11
|
+
# copyright notice, this list of conditions and the following disclaimer
|
12
|
+
# in the documentation and/or other materials provided with the
|
13
|
+
# distribution.
|
14
|
+
# * Neither the name of Google Inc. nor the names of its
|
15
|
+
# contributors may be used to endorse or promote products derived from
|
16
|
+
# this software without specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
19
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
20
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
21
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
22
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
23
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
24
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
25
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
26
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
27
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
|
30
|
+
require "forwardable"
|
31
|
+
require "json"
|
32
|
+
require "signet/oauth_2/client"
|
33
|
+
|
34
|
+
require "googleauth/credentials_loader"
|
35
|
+
|
36
|
+
module Google
|
37
|
+
module Auth
|
38
|
+
##
|
39
|
+
# Credentials is responsible for representing the authentication when connecting to an API. This
|
40
|
+
# class is also intended to be inherited by API-specific classes.
|
41
|
+
class Credentials
|
42
|
+
##
|
43
|
+
# The default token credential URI to be used when none is provided during initialization.
|
44
|
+
TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token".freeze
|
45
|
+
|
46
|
+
##
|
47
|
+
# The default target audience ID to be used when none is provided during initialization.
|
48
|
+
AUDIENCE = "https://oauth2.googleapis.com/token".freeze
|
49
|
+
|
50
|
+
@audience = @scope = @target_audience = @env_vars = @paths = nil
|
51
|
+
|
52
|
+
##
|
53
|
+
# The default token credential URI to be used when none is provided during initialization.
|
54
|
+
# The URI is the authorization server's HTTP endpoint capable of issuing tokens and
|
55
|
+
# refreshing expired tokens.
|
56
|
+
#
|
57
|
+
# @return [String]
|
58
|
+
#
|
59
|
+
def self.token_credential_uri
|
60
|
+
return @token_credential_uri unless @token_credential_uri.nil?
|
61
|
+
|
62
|
+
const_get :TOKEN_CREDENTIAL_URI if const_defined? :TOKEN_CREDENTIAL_URI
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Set the default token credential URI to be used when none is provided during initialization.
|
67
|
+
#
|
68
|
+
# @param [String] new_token_credential_uri
|
69
|
+
# @return [String]
|
70
|
+
#
|
71
|
+
def self.token_credential_uri= new_token_credential_uri
|
72
|
+
@token_credential_uri = new_token_credential_uri
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# The default target audience ID to be used when none is provided during initialization.
|
77
|
+
# Used only by the assertion grant type.
|
78
|
+
#
|
79
|
+
# @return [String]
|
80
|
+
#
|
81
|
+
def self.audience
|
82
|
+
return @audience unless @audience.nil?
|
83
|
+
|
84
|
+
const_get :AUDIENCE if const_defined? :AUDIENCE
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# Sets the default target audience ID to be used when none is provided during initialization.
|
89
|
+
#
|
90
|
+
# @param [String] new_audience
|
91
|
+
# @return [String]
|
92
|
+
#
|
93
|
+
def self.audience= new_audience
|
94
|
+
@audience = new_audience
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# The default scope to be used when none is provided during initialization.
|
99
|
+
# A scope is an access range defined by the authorization server.
|
100
|
+
# The scope can be a single value or a list of values.
|
101
|
+
#
|
102
|
+
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
|
103
|
+
# If {#scope} is set, this credential will produce access tokens.
|
104
|
+
# If {#target_audience} is set, this credential will produce ID tokens.
|
105
|
+
#
|
106
|
+
# @return [String, Array<String>]
|
107
|
+
#
|
108
|
+
def self.scope
|
109
|
+
return @scope unless @scope.nil?
|
110
|
+
|
111
|
+
Array(const_get(:SCOPE)).flatten.uniq if const_defined? :SCOPE
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Sets the default scope to be used when none is provided during initialization.
|
116
|
+
#
|
117
|
+
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
|
118
|
+
# If {#scope} is set, this credential will produce access tokens.
|
119
|
+
# If {#target_audience} is set, this credential will produce ID tokens.
|
120
|
+
#
|
121
|
+
# @param [String, Array<String>] new_scope
|
122
|
+
# @return [String, Array<String>]
|
123
|
+
#
|
124
|
+
def self.scope= new_scope
|
125
|
+
new_scope = Array new_scope unless new_scope.nil?
|
126
|
+
@scope = new_scope
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# The default final target audience for ID tokens, to be used when none
|
131
|
+
# is provided during initialization.
|
132
|
+
#
|
133
|
+
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
|
134
|
+
# If {#scope} is set, this credential will produce access tokens.
|
135
|
+
# If {#target_audience} is set, this credential will produce ID tokens.
|
136
|
+
#
|
137
|
+
# @return [String]
|
138
|
+
#
|
139
|
+
def self.target_audience
|
140
|
+
@target_audience
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# Sets the default final target audience for ID tokens, to be used when none
|
145
|
+
# is provided during initialization.
|
146
|
+
#
|
147
|
+
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
|
148
|
+
# If {#scope} is set, this credential will produce access tokens.
|
149
|
+
# If {#target_audience} is set, this credential will produce ID tokens.
|
150
|
+
#
|
151
|
+
# @param [String] new_target_audience
|
152
|
+
#
|
153
|
+
def self.target_audience= new_target_audience
|
154
|
+
@target_audience = new_target_audience
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# The environment variables to search for credentials. Values can either be a file path to the
|
159
|
+
# credentials file, or the JSON contents of the credentials file.
|
160
|
+
#
|
161
|
+
# @return [Array<String>]
|
162
|
+
#
|
163
|
+
def self.env_vars
|
164
|
+
return @env_vars unless @env_vars.nil?
|
165
|
+
|
166
|
+
# Pull values when PATH_ENV_VARS or JSON_ENV_VARS constants exists.
|
167
|
+
tmp_env_vars = []
|
168
|
+
tmp_env_vars << const_get(:PATH_ENV_VARS) if const_defined? :PATH_ENV_VARS
|
169
|
+
tmp_env_vars << const_get(:JSON_ENV_VARS) if const_defined? :JSON_ENV_VARS
|
170
|
+
tmp_env_vars.flatten.uniq
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# Sets the environment variables to search for credentials.
|
175
|
+
#
|
176
|
+
# @param [Array<String>] new_env_vars
|
177
|
+
# @return [Array<String>]
|
178
|
+
#
|
179
|
+
def self.env_vars= new_env_vars
|
180
|
+
new_env_vars = Array new_env_vars unless new_env_vars.nil?
|
181
|
+
@env_vars = new_env_vars
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# The file paths to search for credentials files.
|
186
|
+
#
|
187
|
+
# @return [Array<String>]
|
188
|
+
#
|
189
|
+
def self.paths
|
190
|
+
return @paths unless @paths.nil?
|
191
|
+
|
192
|
+
tmp_paths = []
|
193
|
+
# Pull in values is the DEFAULT_PATHS constant exists.
|
194
|
+
tmp_paths << const_get(:DEFAULT_PATHS) if const_defined? :DEFAULT_PATHS
|
195
|
+
tmp_paths.flatten.uniq
|
196
|
+
end
|
197
|
+
|
198
|
+
##
|
199
|
+
# Set the file paths to search for credentials files.
|
200
|
+
#
|
201
|
+
# @param [Array<String>] new_paths
|
202
|
+
# @return [Array<String>]
|
203
|
+
#
|
204
|
+
def self.paths= new_paths
|
205
|
+
new_paths = Array new_paths unless new_paths.nil?
|
206
|
+
@paths = new_paths
|
207
|
+
end
|
208
|
+
|
209
|
+
##
|
210
|
+
# The Signet::OAuth2::Client object the Credentials instance is using.
|
211
|
+
#
|
212
|
+
# @return [Signet::OAuth2::Client]
|
213
|
+
#
|
214
|
+
attr_accessor :client
|
215
|
+
|
216
|
+
##
|
217
|
+
# Identifier for the project the client is authenticating with.
|
218
|
+
#
|
219
|
+
# @return [String]
|
220
|
+
#
|
221
|
+
attr_reader :project_id
|
222
|
+
|
223
|
+
##
|
224
|
+
# Identifier for a separate project used for billing/quota, if any.
|
225
|
+
#
|
226
|
+
# @return [String,nil]
|
227
|
+
#
|
228
|
+
attr_reader :quota_project_id
|
229
|
+
|
230
|
+
# @private Delegate client methods to the client object.
|
231
|
+
extend Forwardable
|
232
|
+
|
233
|
+
##
|
234
|
+
# @!attribute [r] token_credential_uri
|
235
|
+
# @return [String] The token credential URI. The URI is the authorization server's HTTP
|
236
|
+
# endpoint capable of issuing tokens and refreshing expired tokens.
|
237
|
+
#
|
238
|
+
# @!attribute [r] audience
|
239
|
+
# @return [String] The target audience ID when issuing assertions. Used only by the
|
240
|
+
# assertion grant type.
|
241
|
+
#
|
242
|
+
# @!attribute [r] scope
|
243
|
+
# @return [String, Array<String>] The scope for this client. A scope is an access range
|
244
|
+
# defined by the authorization server. The scope can be a single value or a list of values.
|
245
|
+
#
|
246
|
+
# @!attribute [r] target_audience
|
247
|
+
# @return [String] The final target audience for ID tokens returned by this credential.
|
248
|
+
#
|
249
|
+
# @!attribute [r] issuer
|
250
|
+
# @return [String] The issuer ID associated with this client.
|
251
|
+
#
|
252
|
+
# @!attribute [r] signing_key
|
253
|
+
# @return [String, OpenSSL::PKey] The signing key associated with this client.
|
254
|
+
#
|
255
|
+
# @!attribute [r] updater_proc
|
256
|
+
# @return [Proc] Returns a reference to the {Signet::OAuth2::Client#apply} method,
|
257
|
+
# suitable for passing as a closure.
|
258
|
+
#
|
259
|
+
def_delegators :@client,
|
260
|
+
:token_credential_uri, :audience,
|
261
|
+
:scope, :issuer, :signing_key, :updater_proc, :target_audience
|
262
|
+
|
263
|
+
##
|
264
|
+
# Creates a new Credentials instance with the provided auth credentials, and with the default
|
265
|
+
# values configured on the class.
|
266
|
+
#
|
267
|
+
# @param [String, Hash, Signet::OAuth2::Client] keyfile
|
268
|
+
# The keyfile can be provided as one of the following:
|
269
|
+
#
|
270
|
+
# * The path to a JSON keyfile (as a +String+)
|
271
|
+
# * The contents of a JSON keyfile (as a +Hash+)
|
272
|
+
# * A +Signet::OAuth2::Client+ object
|
273
|
+
# @param [Hash] options
|
274
|
+
# The options for configuring the credentials instance. The following is supported:
|
275
|
+
#
|
276
|
+
# * +:scope+ - the scope for the client
|
277
|
+
# * +"project_id"+ (and optionally +"project"+) - the project identifier for the client
|
278
|
+
# * +:connection_builder+ - the connection builder to use for the client
|
279
|
+
# * +:default_connection+ - the default connection to use for the client
|
280
|
+
#
|
281
|
+
def initialize keyfile, options = {}
|
282
|
+
verify_keyfile_provided! keyfile
|
283
|
+
@project_id = options["project_id"] || options["project"]
|
284
|
+
@quota_project_id = options["quota_project_id"]
|
285
|
+
if keyfile.is_a? Signet::OAuth2::Client
|
286
|
+
update_from_signet keyfile
|
287
|
+
elsif keyfile.is_a? Hash
|
288
|
+
update_from_hash keyfile, options
|
289
|
+
else
|
290
|
+
update_from_filepath keyfile, options
|
291
|
+
end
|
292
|
+
CredentialsLoader.warn_if_cloud_sdk_credentials @client.client_id
|
293
|
+
@project_id ||= CredentialsLoader.load_gcloud_project_id
|
294
|
+
@client.fetch_access_token!
|
295
|
+
@env_vars = nil
|
296
|
+
@paths = nil
|
297
|
+
@scope = nil
|
298
|
+
end
|
299
|
+
|
300
|
+
##
|
301
|
+
# Creates a new Credentials instance with auth credentials acquired by searching the
|
302
|
+
# environment variables and paths configured on the class, and with the default values
|
303
|
+
# configured on the class.
|
304
|
+
#
|
305
|
+
# The auth credentials are searched for in the following order:
|
306
|
+
#
|
307
|
+
# 1. configured environment variables (see {Credentials.env_vars})
|
308
|
+
# 2. configured default file paths (see {Credentials.paths})
|
309
|
+
# 3. application default (see {Google::Auth.get_application_default})
|
310
|
+
#
|
311
|
+
# @param [Hash] options
|
312
|
+
# The options for configuring the credentials instance. The following is supported:
|
313
|
+
#
|
314
|
+
# * +:scope+ - the scope for the client
|
315
|
+
# * +"project_id"+ (and optionally +"project"+) - the project identifier for the client
|
316
|
+
# * +:connection_builder+ - the connection builder to use for the client
|
317
|
+
# * +:default_connection+ - the default connection to use for the client
|
318
|
+
#
|
319
|
+
# @return [Credentials]
|
320
|
+
#
|
321
|
+
def self.default options = {}
|
322
|
+
# First try to find keyfile file or json from environment variables.
|
323
|
+
client = from_env_vars options
|
324
|
+
|
325
|
+
# Second try to find keyfile file from known file paths.
|
326
|
+
client ||= from_default_paths options
|
327
|
+
|
328
|
+
# Finally get instantiated client from Google::Auth
|
329
|
+
client ||= from_application_default options
|
330
|
+
client
|
331
|
+
end
|
332
|
+
|
333
|
+
##
|
334
|
+
# @private Lookup Credentials from environment variables.
|
335
|
+
def self.from_env_vars options
|
336
|
+
env_vars.each do |env_var|
|
337
|
+
str = ENV[env_var]
|
338
|
+
next if str.nil?
|
339
|
+
return new str, options if ::File.file? str
|
340
|
+
return new ::JSON.parse(str), options rescue nil
|
341
|
+
end
|
342
|
+
nil
|
343
|
+
end
|
344
|
+
|
345
|
+
##
|
346
|
+
# @private Lookup Credentials from default file paths.
|
347
|
+
def self.from_default_paths options
|
348
|
+
paths
|
349
|
+
.select { |p| ::File.file? p }
|
350
|
+
.each do |file|
|
351
|
+
return new file, options
|
352
|
+
end
|
353
|
+
nil
|
354
|
+
end
|
355
|
+
|
356
|
+
##
|
357
|
+
# @private Lookup Credentials using Google::Auth.get_application_default.
|
358
|
+
def self.from_application_default options
|
359
|
+
scope = options[:scope] || self.scope
|
360
|
+
auth_opts = { target_audience: options[:target_audience] || target_audience }
|
361
|
+
client = Google::Auth.get_application_default scope, auth_opts
|
362
|
+
new client, options
|
363
|
+
end
|
364
|
+
|
365
|
+
private_class_method :from_env_vars,
|
366
|
+
:from_default_paths,
|
367
|
+
:from_application_default
|
368
|
+
|
369
|
+
protected
|
370
|
+
|
371
|
+
# Verify that the keyfile argument is provided.
|
372
|
+
def verify_keyfile_provided! keyfile
|
373
|
+
return unless keyfile.nil?
|
374
|
+
raise "The keyfile passed to Google::Auth::Credentials.new was nil."
|
375
|
+
end
|
376
|
+
|
377
|
+
# Verify that the keyfile argument is a file.
|
378
|
+
def verify_keyfile_exists! keyfile
|
379
|
+
exists = ::File.file? keyfile
|
380
|
+
raise "The keyfile '#{keyfile}' is not a valid file." unless exists
|
381
|
+
end
|
382
|
+
|
383
|
+
# Initializes the Signet client.
|
384
|
+
def init_client keyfile, connection_options = {}
|
385
|
+
client_opts = client_options keyfile
|
386
|
+
Signet::OAuth2::Client.new(client_opts)
|
387
|
+
.configure_connection(connection_options)
|
388
|
+
end
|
389
|
+
|
390
|
+
# returns a new Hash with string keys instead of symbol keys.
|
391
|
+
def stringify_hash_keys hash
|
392
|
+
Hash[hash.map { |k, v| [k.to_s, v] }]
|
393
|
+
end
|
394
|
+
|
395
|
+
def client_options options
|
396
|
+
# Keyfile options have higher priority over constructor defaults
|
397
|
+
options["token_credential_uri"] ||= self.class.token_credential_uri
|
398
|
+
options["audience"] ||= self.class.audience
|
399
|
+
options["scope"] ||= self.class.scope
|
400
|
+
options["target_audience"] ||= self.class.target_audience
|
401
|
+
|
402
|
+
if !Array(options["scope"]).empty? && options["target_audience"]
|
403
|
+
raise ArgumentError, "Cannot specify both scope and target_audience"
|
404
|
+
end
|
405
|
+
|
406
|
+
needs_scope = options["target_audience"].nil?
|
407
|
+
# client options for initializing signet client
|
408
|
+
{ token_credential_uri: options["token_credential_uri"],
|
409
|
+
audience: options["audience"],
|
410
|
+
scope: (needs_scope ? Array(options["scope"]) : nil),
|
411
|
+
target_audience: options["target_audience"],
|
412
|
+
issuer: options["client_email"],
|
413
|
+
signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) }
|
414
|
+
end
|
415
|
+
|
416
|
+
def update_from_signet client
|
417
|
+
@project_id ||= client.project_id if client.respond_to? :project_id
|
418
|
+
@quota_project_id ||= client.quota_project_id if client.respond_to? :quota_project_id
|
419
|
+
@client = client
|
420
|
+
end
|
421
|
+
|
422
|
+
def update_from_hash hash, options
|
423
|
+
hash = stringify_hash_keys hash
|
424
|
+
hash["scope"] ||= options[:scope]
|
425
|
+
hash["target_audience"] ||= options[:target_audience]
|
426
|
+
@project_id ||= (hash["project_id"] || hash["project"])
|
427
|
+
@quota_project_id ||= hash["quota_project_id"]
|
428
|
+
@client = init_client hash, options
|
429
|
+
end
|
430
|
+
|
431
|
+
def update_from_filepath path, options
|
432
|
+
verify_keyfile_exists! path
|
433
|
+
json = JSON.parse ::File.read(path)
|
434
|
+
json["scope"] ||= options[:scope]
|
435
|
+
json["target_audience"] ||= options[:target_audience]
|
436
|
+
@project_id ||= (json["project_id"] || json["project"])
|
437
|
+
@quota_project_id ||= json["quota_project_id"]
|
438
|
+
@client = init_client json, options
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
@@ -27,9 +27,9 @@
|
|
27
27
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
28
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
29
|
|
30
|
-
require
|
31
|
-
require
|
32
|
-
require
|
30
|
+
require "memoist"
|
31
|
+
require "os"
|
32
|
+
require "rbconfig"
|
33
33
|
|
34
34
|
module Google
|
35
35
|
# Module Auth provides classes that provide Google-specific authorization
|
@@ -39,44 +39,71 @@ module Google
|
|
39
39
|
# credentials files on the file system.
|
40
40
|
module CredentialsLoader
|
41
41
|
extend Memoist
|
42
|
-
ENV_VAR
|
42
|
+
ENV_VAR = "GOOGLE_APPLICATION_CREDENTIALS".freeze
|
43
|
+
PRIVATE_KEY_VAR = "GOOGLE_PRIVATE_KEY".freeze
|
44
|
+
CLIENT_EMAIL_VAR = "GOOGLE_CLIENT_EMAIL".freeze
|
45
|
+
CLIENT_ID_VAR = "GOOGLE_CLIENT_ID".freeze
|
46
|
+
CLIENT_SECRET_VAR = "GOOGLE_CLIENT_SECRET".freeze
|
47
|
+
REFRESH_TOKEN_VAR = "GOOGLE_REFRESH_TOKEN".freeze
|
48
|
+
ACCOUNT_TYPE_VAR = "GOOGLE_ACCOUNT_TYPE".freeze
|
49
|
+
PROJECT_ID_VAR = "GOOGLE_PROJECT_ID".freeze
|
50
|
+
GCLOUD_POSIX_COMMAND = "gcloud".freeze
|
51
|
+
GCLOUD_WINDOWS_COMMAND = "gcloud.cmd".freeze
|
52
|
+
GCLOUD_CONFIG_COMMAND =
|
53
|
+
"config config-helper --format json --verbosity none".freeze
|
43
54
|
|
44
|
-
|
45
|
-
CLIENT_EMAIL_VAR = 'GOOGLE_CLIENT_EMAIL'
|
46
|
-
CLIENT_ID_VAR = 'GOOGLE_CLIENT_ID'
|
47
|
-
CLIENT_SECRET_VAR = 'GOOGLE_CLIENT_SECRET'
|
48
|
-
REFRESH_TOKEN_VAR = 'GOOGLE_REFRESH_TOKEN'
|
49
|
-
ACCOUNT_TYPE_VAR = 'GOOGLE_ACCOUNT_TYPE'
|
50
|
-
|
51
|
-
CREDENTIALS_FILE_NAME = 'application_default_credentials.json'
|
55
|
+
CREDENTIALS_FILE_NAME = "application_default_credentials.json".freeze
|
52
56
|
NOT_FOUND_ERROR =
|
53
|
-
"Unable to read the credential file specified by #{ENV_VAR}"
|
54
|
-
WELL_KNOWN_PATH = "gcloud/#{CREDENTIALS_FILE_NAME}"
|
55
|
-
WELL_KNOWN_ERROR =
|
57
|
+
"Unable to read the credential file specified by #{ENV_VAR}".freeze
|
58
|
+
WELL_KNOWN_PATH = "gcloud/#{CREDENTIALS_FILE_NAME}".freeze
|
59
|
+
WELL_KNOWN_ERROR = "Unable to read the default credential file".freeze
|
60
|
+
|
61
|
+
SYSTEM_DEFAULT_ERROR =
|
62
|
+
"Unable to read the system default credential file".freeze
|
63
|
+
|
64
|
+
CLOUD_SDK_CLIENT_ID = "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.app"\
|
65
|
+
"s.googleusercontent.com".freeze
|
56
66
|
|
57
|
-
|
67
|
+
CLOUD_SDK_CREDENTIALS_WARNING = "Your application has authenticated using end user "\
|
68
|
+
"credentials from Google Cloud SDK. We recommend that most server applications use "\
|
69
|
+
"service accounts instead. If your application continues to use end user credentials "\
|
70
|
+
'from Cloud SDK, you might receive a "quota exceeded" or "API not enabled" error. For '\
|
71
|
+
"more information about service accounts, see "\
|
72
|
+
"https://cloud.google.com/docs/authentication/. To suppress this message, set the "\
|
73
|
+
"GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS environment variable.".freeze
|
58
74
|
|
59
75
|
# make_creds proxies the construction of a credentials instance
|
60
76
|
#
|
61
77
|
# By default, it calls #new on the current class, but this behaviour can
|
62
78
|
# be modified, allowing different instances to be created.
|
63
|
-
def make_creds
|
64
|
-
new(*args)
|
79
|
+
def make_creds *args
|
80
|
+
creds = new(*args)
|
81
|
+
creds = creds.configure_connection args[0] if creds.respond_to?(:configure_connection) && args.size == 1
|
82
|
+
creds
|
65
83
|
end
|
66
84
|
|
67
85
|
# Creates an instance from the path specified in an environment
|
68
86
|
# variable.
|
69
87
|
#
|
70
88
|
# @param scope [string|array|nil] the scope(s) to access
|
71
|
-
|
72
|
-
|
89
|
+
# @param options [Hash] Connection options. These may be used to configure
|
90
|
+
# how OAuth tokens are retrieved, by providing a suitable
|
91
|
+
# `Faraday::Connection`. For example, if a connection proxy must be
|
92
|
+
# used in the current network, you may provide a connection with
|
93
|
+
# with the needed proxy options.
|
94
|
+
# The following keys are recognized:
|
95
|
+
# * `:default_connection` The connection object to use.
|
96
|
+
# * `:connection_builder` A `Proc` that returns a connection.
|
97
|
+
def from_env scope = nil, options = {}
|
98
|
+
options = interpret_options scope, options
|
99
|
+
if ENV.key?(ENV_VAR) && !ENV[ENV_VAR].empty?
|
73
100
|
path = ENV[ENV_VAR]
|
74
|
-
|
75
|
-
File.open
|
76
|
-
return make_creds(json_key_io: f
|
101
|
+
raise "file #{path} does not exist" unless File.exist? path
|
102
|
+
File.open path do |f|
|
103
|
+
return make_creds options.merge(json_key_io: f)
|
77
104
|
end
|
78
105
|
elsif service_account_env_vars? || authorized_user_env_vars?
|
79
|
-
return make_creds
|
106
|
+
return make_creds options
|
80
107
|
end
|
81
108
|
rescue StandardError => e
|
82
109
|
raise "#{NOT_FOUND_ERROR}: #{e}"
|
@@ -85,15 +112,24 @@ module Google
|
|
85
112
|
# Creates an instance from a well known path.
|
86
113
|
#
|
87
114
|
# @param scope [string|array|nil] the scope(s) to access
|
88
|
-
|
89
|
-
|
115
|
+
# @param options [Hash] Connection options. These may be used to configure
|
116
|
+
# how OAuth tokens are retrieved, by providing a suitable
|
117
|
+
# `Faraday::Connection`. For example, if a connection proxy must be
|
118
|
+
# used in the current network, you may provide a connection with
|
119
|
+
# with the needed proxy options.
|
120
|
+
# The following keys are recognized:
|
121
|
+
# * `:default_connection` The connection object to use.
|
122
|
+
# * `:connection_builder` A `Proc` that returns a connection.
|
123
|
+
def from_well_known_path scope = nil, options = {}
|
124
|
+
options = interpret_options scope, options
|
125
|
+
home_var = OS.windows? ? "APPDATA" : "HOME"
|
90
126
|
base = WELL_KNOWN_PATH
|
91
|
-
root = ENV[home_var].nil? ?
|
92
|
-
base = File.join
|
93
|
-
path = File.join
|
94
|
-
return nil unless File.exist?
|
95
|
-
File.open
|
96
|
-
return make_creds(json_key_io: f
|
127
|
+
root = ENV[home_var].nil? ? "" : ENV[home_var]
|
128
|
+
base = File.join ".config", base unless OS.windows?
|
129
|
+
path = File.join root, base
|
130
|
+
return nil unless File.exist? path
|
131
|
+
File.open path do |f|
|
132
|
+
return make_creds options.merge(json_key_io: f)
|
97
133
|
end
|
98
134
|
rescue StandardError => e
|
99
135
|
raise "#{WELL_KNOWN_ERROR}: #{e}"
|
@@ -102,31 +138,69 @@ module Google
|
|
102
138
|
# Creates an instance from the system default path
|
103
139
|
#
|
104
140
|
# @param scope [string|array|nil] the scope(s) to access
|
105
|
-
|
141
|
+
# @param options [Hash] Connection options. These may be used to configure
|
142
|
+
# how OAuth tokens are retrieved, by providing a suitable
|
143
|
+
# `Faraday::Connection`. For example, if a connection proxy must be
|
144
|
+
# used in the current network, you may provide a connection with
|
145
|
+
# with the needed proxy options.
|
146
|
+
# The following keys are recognized:
|
147
|
+
# * `:default_connection` The connection object to use.
|
148
|
+
# * `:connection_builder` A `Proc` that returns a connection.
|
149
|
+
def from_system_default_path scope = nil, options = {}
|
150
|
+
options = interpret_options scope, options
|
106
151
|
if OS.windows?
|
107
|
-
return nil unless ENV[
|
108
|
-
prefix = File.join
|
152
|
+
return nil unless ENV["ProgramData"]
|
153
|
+
prefix = File.join ENV["ProgramData"], "Google/Auth"
|
109
154
|
else
|
110
|
-
prefix =
|
155
|
+
prefix = "/etc/google/auth/"
|
111
156
|
end
|
112
|
-
path = File.join
|
113
|
-
return nil unless File.exist?
|
114
|
-
File.open
|
115
|
-
return make_creds(json_key_io: f
|
157
|
+
path = File.join prefix, CREDENTIALS_FILE_NAME
|
158
|
+
return nil unless File.exist? path
|
159
|
+
File.open path do |f|
|
160
|
+
return make_creds options.merge(json_key_io: f)
|
116
161
|
end
|
117
162
|
rescue StandardError => e
|
118
163
|
raise "#{SYSTEM_DEFAULT_ERROR}: #{e}"
|
119
164
|
end
|
120
165
|
|
166
|
+
module_function
|
167
|
+
|
168
|
+
# Issues warning if cloud sdk client id is used
|
169
|
+
def warn_if_cloud_sdk_credentials client_id
|
170
|
+
return if ENV["GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS"]
|
171
|
+
warn CLOUD_SDK_CREDENTIALS_WARNING if client_id == CLOUD_SDK_CLIENT_ID
|
172
|
+
end
|
173
|
+
|
174
|
+
# Finds project_id from gcloud CLI configuration
|
175
|
+
def load_gcloud_project_id
|
176
|
+
gcloud = GCLOUD_WINDOWS_COMMAND if OS.windows?
|
177
|
+
gcloud = GCLOUD_POSIX_COMMAND unless OS.windows?
|
178
|
+
gcloud_json = IO.popen("#{gcloud} #{GCLOUD_CONFIG_COMMAND}", &:read)
|
179
|
+
config = MultiJson.load gcloud_json
|
180
|
+
config["configuration"]["properties"]["core"]["project"]
|
181
|
+
rescue StandardError
|
182
|
+
nil
|
183
|
+
end
|
184
|
+
|
121
185
|
private
|
122
186
|
|
187
|
+
def interpret_options scope, options
|
188
|
+
if scope.is_a? Hash
|
189
|
+
options = scope
|
190
|
+
scope = nil
|
191
|
+
end
|
192
|
+
return options.merge scope: scope if scope && !options[:scope]
|
193
|
+
options
|
194
|
+
end
|
195
|
+
|
123
196
|
def service_account_env_vars?
|
124
|
-
([PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR] - ENV.keys).empty?
|
197
|
+
([PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR] - ENV.keys).empty? &&
|
198
|
+
!ENV.to_h.fetch_values(PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR).join(" ").empty?
|
125
199
|
end
|
126
200
|
|
127
201
|
def authorized_user_env_vars?
|
128
|
-
([CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR] -
|
129
|
-
ENV.
|
202
|
+
([CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR] - ENV.keys).empty? &&
|
203
|
+
!ENV.to_h.fetch_values(CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR).join(" ").empty?
|
130
204
|
end
|
131
205
|
end
|
132
206
|
end
|