googleauth 0.5.1 → 0.11.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.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +5 -4
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +36 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
  5. data/.github/ISSUE_TEMPLATE/support_request.md +7 -0
  6. data/.kokoro/build.bat +16 -0
  7. data/.kokoro/build.sh +4 -0
  8. data/.kokoro/continuous/common.cfg +24 -0
  9. data/.kokoro/continuous/linux.cfg +25 -0
  10. data/.kokoro/continuous/osx.cfg +8 -0
  11. data/.kokoro/continuous/post.cfg +30 -0
  12. data/.kokoro/continuous/windows.cfg +29 -0
  13. data/.kokoro/osx.sh +4 -0
  14. data/.kokoro/presubmit/common.cfg +24 -0
  15. data/.kokoro/presubmit/linux.cfg +24 -0
  16. data/.kokoro/presubmit/osx.cfg +8 -0
  17. data/.kokoro/presubmit/windows.cfg +29 -0
  18. data/.kokoro/release.cfg +94 -0
  19. data/.kokoro/trampoline.bat +10 -0
  20. data/.kokoro/trampoline.sh +4 -0
  21. data/.repo-metadata.json +5 -0
  22. data/.rubocop.yml +17 -1
  23. data/CHANGELOG.md +90 -19
  24. data/CODE_OF_CONDUCT.md +43 -0
  25. data/Gemfile +16 -13
  26. data/README.md +58 -18
  27. data/Rakefile +106 -10
  28. data/googleauth.gemspec +27 -25
  29. data/lib/googleauth/application_default.rb +81 -0
  30. data/lib/googleauth/client_id.rb +21 -19
  31. data/lib/googleauth/compute_engine.rb +40 -43
  32. data/lib/googleauth/credentials.rb +375 -0
  33. data/lib/googleauth/credentials_loader.rb +117 -43
  34. data/lib/googleauth/default_credentials.rb +93 -0
  35. data/lib/googleauth/iam.rb +11 -11
  36. data/lib/googleauth/json_key_reader.rb +46 -0
  37. data/lib/googleauth/scope_util.rb +12 -12
  38. data/lib/googleauth/service_account.rb +64 -62
  39. data/lib/googleauth/signet.rb +53 -12
  40. data/lib/googleauth/stores/file_token_store.rb +8 -8
  41. data/lib/googleauth/stores/redis_token_store.rb +22 -22
  42. data/lib/googleauth/token_store.rb +6 -6
  43. data/lib/googleauth/user_authorizer.rb +80 -68
  44. data/lib/googleauth/user_refresh.rb +44 -35
  45. data/lib/googleauth/version.rb +1 -1
  46. data/lib/googleauth/web_user_authorizer.rb +77 -68
  47. data/lib/googleauth.rb +6 -96
  48. data/rakelib/devsite_builder.rb +45 -0
  49. data/rakelib/link_checker.rb +64 -0
  50. data/rakelib/repo_metadata.rb +59 -0
  51. data/spec/googleauth/apply_auth_examples.rb +47 -46
  52. data/spec/googleauth/client_id_spec.rb +75 -55
  53. data/spec/googleauth/compute_engine_spec.rb +60 -43
  54. data/spec/googleauth/credentials_spec.rb +467 -0
  55. data/spec/googleauth/get_application_default_spec.rb +149 -111
  56. data/spec/googleauth/iam_spec.rb +25 -25
  57. data/spec/googleauth/scope_util_spec.rb +26 -24
  58. data/spec/googleauth/service_account_spec.rb +261 -143
  59. data/spec/googleauth/signet_spec.rb +93 -30
  60. data/spec/googleauth/stores/file_token_store_spec.rb +12 -13
  61. data/spec/googleauth/stores/redis_token_store_spec.rb +11 -11
  62. data/spec/googleauth/stores/store_examples.rb +16 -16
  63. data/spec/googleauth/user_authorizer_spec.rb +153 -124
  64. data/spec/googleauth/user_refresh_spec.rb +186 -121
  65. data/spec/googleauth/web_user_authorizer_spec.rb +82 -69
  66. data/spec/spec_helper.rb +21 -19
  67. metadata +75 -32
  68. data/.rubocop_todo.yml +0 -32
  69. data/.travis.yml +0 -37
@@ -0,0 +1,375 @@
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
+ ##
51
+ # The default token credential URI to be used when none is provided during initialization.
52
+ # The URI is the authorization server's HTTP endpoint capable of issuing tokens and
53
+ # refreshing expired tokens.
54
+ #
55
+ # @return [String]
56
+ #
57
+ def self.token_credential_uri
58
+ return @token_credential_uri unless @token_credential_uri.nil?
59
+
60
+ const_get :TOKEN_CREDENTIAL_URI if const_defined? :TOKEN_CREDENTIAL_URI
61
+ end
62
+
63
+ ##
64
+ # Set the default token credential URI to be used when none is provided during initialization.
65
+ #
66
+ # @param [String] new_token_credential_uri
67
+ # @return [String]
68
+ #
69
+ def self.token_credential_uri= new_token_credential_uri
70
+ @token_credential_uri = new_token_credential_uri
71
+ end
72
+
73
+ ##
74
+ # The default target audience ID to be used when none is provided during initialization.
75
+ # Used only by the assertion grant type.
76
+ #
77
+ # @return [String]
78
+ #
79
+ def self.audience
80
+ return @audience unless @audience.nil?
81
+
82
+ const_get :AUDIENCE if const_defined? :AUDIENCE
83
+ end
84
+
85
+ ##
86
+ # Sets the default target audience ID to be used when none is provided during initialization.
87
+ #
88
+ # @param [String] new_audience
89
+ # @return [String]
90
+ #
91
+ def self.audience= new_audience
92
+ @audience = new_audience
93
+ end
94
+
95
+ ##
96
+ # The default scope to be used when none is provided during initialization.
97
+ # A scope is an access range defined by the authorization server.
98
+ # The scope can be a single value or a list of values.
99
+ #
100
+ # @return [String, Array<String>]
101
+ #
102
+ def self.scope
103
+ return @scope unless @scope.nil?
104
+
105
+ tmp_scope = []
106
+ # Pull in values is the SCOPE constant exists.
107
+ tmp_scope << const_get(:SCOPE) if const_defined? :SCOPE
108
+ tmp_scope.flatten.uniq
109
+ end
110
+
111
+ ##
112
+ # Sets the default scope to be used when none is provided during initialization.
113
+ #
114
+ # @param [String, Array<String>] new_scope
115
+ # @return [String, Array<String>]
116
+ #
117
+ def self.scope= new_scope
118
+ new_scope = Array new_scope unless new_scope.nil?
119
+ @scope = new_scope
120
+ end
121
+
122
+ ##
123
+ # The environment variables to search for credentials. Values can either be a file path to the
124
+ # credentials file, or the JSON contents of the credentials file.
125
+ #
126
+ # @return [Array<String>]
127
+ #
128
+ def self.env_vars
129
+ return @env_vars unless @env_vars.nil?
130
+
131
+ # Pull values when PATH_ENV_VARS or JSON_ENV_VARS constants exists.
132
+ tmp_env_vars = []
133
+ tmp_env_vars << const_get(:PATH_ENV_VARS) if const_defined? :PATH_ENV_VARS
134
+ tmp_env_vars << const_get(:JSON_ENV_VARS) if const_defined? :JSON_ENV_VARS
135
+ tmp_env_vars.flatten.uniq
136
+ end
137
+
138
+ ##
139
+ # Sets the environment variables to search for credentials.
140
+ #
141
+ # @param [Array<String>] new_env_vars
142
+ # @return [Array<String>]
143
+ #
144
+ def self.env_vars= new_env_vars
145
+ new_env_vars = Array new_env_vars unless new_env_vars.nil?
146
+ @env_vars = new_env_vars
147
+ end
148
+
149
+ ##
150
+ # The file paths to search for credentials files.
151
+ #
152
+ # @return [Array<String>]
153
+ #
154
+ def self.paths
155
+ return @paths unless @paths.nil?
156
+
157
+ tmp_paths = []
158
+ # Pull in values is the DEFAULT_PATHS constant exists.
159
+ tmp_paths << const_get(:DEFAULT_PATHS) if const_defined? :DEFAULT_PATHS
160
+ tmp_paths.flatten.uniq
161
+ end
162
+
163
+ ##
164
+ # Set the file paths to search for credentials files.
165
+ #
166
+ # @param [Array<String>] new_paths
167
+ # @return [Array<String>]
168
+ #
169
+ def self.paths= new_paths
170
+ new_paths = Array new_paths unless new_paths.nil?
171
+ @paths = new_paths
172
+ end
173
+
174
+ ##
175
+ # The Signet::OAuth2::Client object the Credentials instance is using.
176
+ #
177
+ # @return [Signet::OAuth2::Client]
178
+ #
179
+ attr_accessor :client
180
+
181
+ ##
182
+ # Identifier for the project the client is authenticating with.
183
+ #
184
+ # @return [String]
185
+ #
186
+ attr_reader :project_id
187
+
188
+ # @private Delegate client methods to the client object.
189
+ extend Forwardable
190
+
191
+ ##
192
+ # @!attribute [r] token_credential_uri
193
+ # @return [String] The token credential URI. The URI is the authorization server's HTTP
194
+ # endpoint capable of issuing tokens and refreshing expired tokens.
195
+ #
196
+ # @!attribute [r] audience
197
+ # @return [String] The target audience ID when issuing assertions. Used only by the
198
+ # assertion grant type.
199
+ #
200
+ # @!attribute [r] scope
201
+ # @return [String, Array<String>] The scope for this client. A scope is an access range
202
+ # defined by the authorization server. The scope can be a single value or a list of values.
203
+ #
204
+ # @!attribute [r] issuer
205
+ # @return [String] The issuer ID associated with this client.
206
+ #
207
+ # @!attribute [r] signing_key
208
+ # @return [String, OpenSSL::PKey] The signing key associated with this client.
209
+ #
210
+ # @!attribute [r] updater_proc
211
+ # @return [Proc] Returns a reference to the {Signet::OAuth2::Client#apply} method,
212
+ # suitable for passing as a closure.
213
+ #
214
+ def_delegators :@client,
215
+ :token_credential_uri, :audience,
216
+ :scope, :issuer, :signing_key, :updater_proc
217
+
218
+ # rubocop:disable Metrics/AbcSize
219
+
220
+ ##
221
+ # Creates a new Credentials instance with the provided auth credentials, and with the default
222
+ # values configured on the class.
223
+ #
224
+ # @param [String, Hash, Signet::OAuth2::Client] keyfile
225
+ # The keyfile can be provided as one of the following:
226
+ #
227
+ # * The path to a JSON keyfile (as a +String+)
228
+ # * The contents of a JSON keyfile (as a +Hash+)
229
+ # * A +Signet::OAuth2::Client+ object
230
+ # @param [Hash] options
231
+ # The options for configuring the credentials instance. The following is supported:
232
+ #
233
+ # * +:scope+ - the scope for the client
234
+ # * +"project_id"+ (and optionally +"project"+) - the project identifier for the client
235
+ # * +:connection_builder+ - the connection builder to use for the client
236
+ # * +:default_connection+ - the default connection to use for the client
237
+ #
238
+ def initialize keyfile, options = {}
239
+ scope = options[:scope]
240
+ verify_keyfile_provided! keyfile
241
+ @project_id = options["project_id"] || options["project"]
242
+ if keyfile.is_a? Signet::OAuth2::Client
243
+ @client = keyfile
244
+ @project_id ||= keyfile.project_id if keyfile.respond_to? :project_id
245
+ elsif keyfile.is_a? Hash
246
+ hash = stringify_hash_keys keyfile
247
+ hash["scope"] ||= scope
248
+ @client = init_client hash, options
249
+ @project_id ||= (hash["project_id"] || hash["project"])
250
+ else
251
+ verify_keyfile_exists! keyfile
252
+ json = JSON.parse ::File.read(keyfile)
253
+ json["scope"] ||= scope
254
+ @project_id ||= (json["project_id"] || json["project"])
255
+ @client = init_client json, options
256
+ end
257
+ CredentialsLoader.warn_if_cloud_sdk_credentials @client.client_id
258
+ @project_id ||= CredentialsLoader.load_gcloud_project_id
259
+ @client.fetch_access_token!
260
+ @env_vars = nil
261
+ @paths = nil
262
+ @scope = nil
263
+ end
264
+ # rubocop:enable Metrics/AbcSize
265
+
266
+ ##
267
+ # Creates a new Credentials instance with auth credentials acquired by searching the
268
+ # environment variables and paths configured on the class, and with the default values
269
+ # configured on the class.
270
+ #
271
+ # The auth credentials are searched for in the following order:
272
+ #
273
+ # 1. configured environment variables (see {Credentials.env_vars})
274
+ # 2. configured default file paths (see {Credentials.paths})
275
+ # 3. application default (see {Google::Auth.get_application_default})
276
+ #
277
+ # @param [Hash] options
278
+ # The options for configuring the credentials instance. The following is supported:
279
+ #
280
+ # * +:scope+ - the scope for the client
281
+ # * +"project_id"+ (and optionally +"project"+) - the project identifier for the client
282
+ # * +:connection_builder+ - the connection builder to use for the client
283
+ # * +:default_connection+ - the default connection to use for the client
284
+ #
285
+ # @return [Credentials]
286
+ #
287
+ def self.default options = {}
288
+ # First try to find keyfile file or json from environment variables.
289
+ client = from_env_vars options
290
+
291
+ # Second try to find keyfile file from known file paths.
292
+ client ||= from_default_paths options
293
+
294
+ # Finally get instantiated client from Google::Auth
295
+ client ||= from_application_default options
296
+ client
297
+ end
298
+
299
+ ##
300
+ # @private Lookup Credentials from environment variables.
301
+ def self.from_env_vars options
302
+ env_vars.each do |env_var|
303
+ str = ENV[env_var]
304
+ next if str.nil?
305
+ return new str, options if ::File.file? str
306
+ return new ::JSON.parse(str), options rescue nil
307
+ end
308
+ nil
309
+ end
310
+
311
+ ##
312
+ # @private Lookup Credentials from default file paths.
313
+ def self.from_default_paths options
314
+ paths
315
+ .select { |p| ::File.file? p }
316
+ .each do |file|
317
+ return new file, options
318
+ end
319
+ nil
320
+ end
321
+
322
+ ##
323
+ # @private Lookup Credentials using Google::Auth.get_application_default.
324
+ def self.from_application_default options
325
+ scope = options[:scope] || self.scope
326
+ client = Google::Auth.get_application_default scope
327
+ new client, options
328
+ end
329
+
330
+ private_class_method :from_env_vars,
331
+ :from_default_paths,
332
+ :from_application_default
333
+
334
+ protected
335
+
336
+ # Verify that the keyfile argument is provided.
337
+ def verify_keyfile_provided! keyfile
338
+ return unless keyfile.nil?
339
+ raise "The keyfile passed to Google::Auth::Credentials.new was nil."
340
+ end
341
+
342
+ # Verify that the keyfile argument is a file.
343
+ def verify_keyfile_exists! keyfile
344
+ exists = ::File.file? keyfile
345
+ raise "The keyfile '#{keyfile}' is not a valid file." unless exists
346
+ end
347
+
348
+ # Initializes the Signet client.
349
+ def init_client keyfile, connection_options = {}
350
+ client_opts = client_options keyfile
351
+ Signet::OAuth2::Client.new(client_opts)
352
+ .configure_connection(connection_options)
353
+ end
354
+
355
+ # returns a new Hash with string keys instead of symbol keys.
356
+ def stringify_hash_keys hash
357
+ Hash[hash.map { |k, v| [k.to_s, v] }]
358
+ end
359
+
360
+ def client_options options
361
+ # Keyfile options have higher priority over constructor defaults
362
+ options["token_credential_uri"] ||= self.class.token_credential_uri
363
+ options["audience"] ||= self.class.audience
364
+ options["scope"] ||= self.class.scope
365
+
366
+ # client options for initializing signet client
367
+ { token_credential_uri: options["token_credential_uri"],
368
+ audience: options["audience"],
369
+ scope: Array(options["scope"]),
370
+ issuer: options["client_email"],
371
+ signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) }
372
+ end
373
+ end
374
+ end
375
+ 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 'memoist'
31
- require 'os'
32
- require 'rbconfig'
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 = 'GOOGLE_APPLICATION_CREDENTIALS'
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
- PRIVATE_KEY_VAR = 'GOOGLE_PRIVATE_KEY'
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 = 'Unable to read the default credential file'
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
- SYSTEM_DEFAULT_ERROR = 'Unable to read the system default credential file'
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(*args)
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
- def from_env(scope = nil)
72
- if ENV.key?(ENV_VAR)
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
- fail "file #{path} does not exist" unless File.exist?(path)
75
- File.open(path) do |f|
76
- return make_creds(json_key_io: f, scope: scope)
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(scope: scope)
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
- def from_well_known_path(scope = nil)
89
- home_var = OS.windows? ? 'APPDATA' : 'HOME'
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? ? '' : ENV[home_var]
92
- base = File.join('.config', base) unless OS.windows?
93
- path = File.join(root, base)
94
- return nil unless File.exist?(path)
95
- File.open(path) do |f|
96
- return make_creds(json_key_io: f, scope: scope)
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
- def from_system_default_path(scope = nil)
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['ProgramData']
108
- prefix = File.join(ENV['ProgramData'], 'Google/Auth')
152
+ return nil unless ENV["ProgramData"]
153
+ prefix = File.join ENV["ProgramData"], "Google/Auth"
109
154
  else
110
- prefix = '/etc/google/auth/'
155
+ prefix = "/etc/google/auth/"
111
156
  end
112
- path = File.join(prefix, CREDENTIALS_FILE_NAME)
113
- return nil unless File.exist?(path)
114
- File.open(path) do |f|
115
- return make_creds(json_key_io: f, scope: scope)
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.keys).empty?
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
@@ -0,0 +1,93 @@
1
+ # Copyright 2015, 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 "multi_json"
31
+ require "stringio"
32
+
33
+ require "googleauth/credentials_loader"
34
+ require "googleauth/service_account"
35
+ require "googleauth/user_refresh"
36
+
37
+ module Google
38
+ # Module Auth provides classes that provide Google-specific authorization
39
+ # used to access Google APIs.
40
+ module Auth
41
+ # DefaultCredentials is used to preload the credentials file, to determine
42
+ # which type of credentials should be loaded.
43
+ class DefaultCredentials
44
+ extend CredentialsLoader
45
+
46
+ # override CredentialsLoader#make_creds to use the class determined by
47
+ # loading the json.
48
+ def self.make_creds options = {}
49
+ json_key_io = options[:json_key_io]
50
+ if json_key_io
51
+ json_key, clz = determine_creds_class json_key_io
52
+ warn_if_cloud_sdk_credentials json_key["client_id"]
53
+ io = StringIO.new MultiJson.dump(json_key)
54
+ clz.make_creds options.merge(json_key_io: io)
55
+ else
56
+ warn_if_cloud_sdk_credentials ENV[CredentialsLoader::CLIENT_ID_VAR]
57
+ clz = read_creds
58
+ clz.make_creds options
59
+ end
60
+ end
61
+
62
+ def self.read_creds
63
+ env_var = CredentialsLoader::ACCOUNT_TYPE_VAR
64
+ type = ENV[env_var]
65
+ raise "#{env_var} is undefined in env" unless type
66
+ case type
67
+ when "service_account"
68
+ ServiceAccountCredentials
69
+ when "authorized_user"
70
+ UserRefreshCredentials
71
+ else
72
+ raise "credentials type '#{type}' is not supported"
73
+ end
74
+ end
75
+
76
+ # Reads the input json and determines which creds class to use.
77
+ def self.determine_creds_class json_key_io
78
+ json_key = MultiJson.load json_key_io.read
79
+ key = "type"
80
+ raise "the json is missing the '#{key}' field" unless json_key.key? key
81
+ type = json_key[key]
82
+ case type
83
+ when "service_account"
84
+ [json_key, ServiceAccountCredentials]
85
+ when "authorized_user"
86
+ [json_key, UserRefreshCredentials]
87
+ else
88
+ raise "credentials type '#{type}' is not supported"
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end