googleauth 0.5.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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