googleauth 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8846e57d325ff993c15ca691e299b9c2c4b7472b1b0a9e905b36cdb99216e061
4
- data.tar.gz: 2fcee29e36a6fd57420b9cd0106cf3ab73bf447e94e2f6bdce61a973d256cd5e
3
+ metadata.gz: b15478e865e5cfea5a21aaf18b55e6f7839c0c6a81fd127a249d414ce7f62589
4
+ data.tar.gz: 0a5ea3ff83f4706367b710ac200ef1936f042f8c124174c0ab5857aa435e940c
5
5
  SHA512:
6
- metadata.gz: dd54bce055240fc1db34ccfe2850ab49f23b17f55f5336dfeccf380c2f93b8b9e29100a1c53f360564e8387805a9c4bf74d09eb2ca58b5bda666cdab3b061f45
7
- data.tar.gz: 27dae4439e8163194604e912918709d2cd623c61856f70f7c350b08dfac010fdff50ad703934b88631c2759dcf7e5aab5b315a884cb160790c153115ee88bdfe
6
+ metadata.gz: a096b40f4f8559d1263e9f7fd8d28742ee63199dfc5ae77c486602f14bb9f03ed1331b86e64a0afe32bcafef38bb9e26140226615ac546817e1bc8d4c96812ba
7
+ data.tar.gz: fefa616d20dbfb6b11b71e7869d08e470b919cce6ea991d91930f5036f2945fb652cbb6dfe83f50e93c626c953ba4d9b82483b28891abc97bbb8f4fae863013c
@@ -0,0 +1,36 @@
1
+ on:
2
+ schedule:
3
+ - cron: '29 9 * * 1'
4
+ workflow_dispatch:
5
+
6
+ name: release
7
+ jobs:
8
+ release-please:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - name: ReleasePlease
12
+ id: release-please
13
+ uses: GoogleCloudPlatform/release-please-action@v2
14
+ with:
15
+ command: release-pr
16
+ token: ${{ secrets.YOSHI_CODE_BOT_TOKEN }}
17
+ fork: true
18
+ release-type: ruby
19
+ package-name: google-auth-library-ruby
20
+ version-file: lib/googleauth/version.rb
21
+ monorepo-tags: true
22
+ bump-minor-pre-major: true
23
+ - name: ReleaseLabel
24
+ id: release-label
25
+ if: ${{ steps.release-please.outputs.pr }}
26
+ uses: actions/github-script@v2
27
+ with:
28
+ github-token: ${{secrets.YOSHI_APPROVER_TOKEN}}
29
+ script: |
30
+ core.info("Labeling release");
31
+ github.issues.addLabels({
32
+ owner: 'googleapis',
33
+ repo: 'google-auth-library-ruby',
34
+ issue_number: ${{ steps.release-please.outputs.pr }},
35
+ labels: ["autorelease: pending", "kokoro:force-run"]
36
+ });
@@ -1,30 +1,44 @@
1
1
  # Release History
2
2
 
3
- ### 0.14.0 / 2020-10-09
3
+ ## [0.15.0](https://www.github.com/googleapis/google-auth-library-ruby/compare/v0.14.0...v0.15.0) (2021-01-26)
4
+
5
+
6
+ ### Features
7
+
8
+ * Credential parameters inherit from superclasses ([4fa4720](https://www.github.com/googleapis/google-auth-library-ruby/commit/4fa47206dbd62f8bbdd1b9d3721f6baee9fd1d62))
9
+ * Service accounts apply a self-signed JWT if scopes are marked as default ([d22acb8](https://www.github.com/googleapis/google-auth-library-ruby/commit/d22acb8a510e6711b5674545c31a4816e5a9168f))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * Retry fetch_access_token when GCE metadata server returns unexpected errors ([cd9b012](https://www.github.com/googleapis/google-auth-library-ruby/commit/cd9b0126d3419b9953982f71edc9e6ba3f640e3c))
15
+ * Support correct service account and user refresh behavior for custom credential env variables ([d2dffe5](https://www.github.com/googleapis/google-auth-library-ruby/commit/d2dffe592112b45006291ad9a57f56e00fb208c3))
16
+
17
+ ## 0.14.0 / 2020-10-09
4
18
 
5
19
  * Honor GCE_METADATA_HOST environment variable
6
20
  * Fix errors in some environments when requesting an access token for multiple scopes
7
21
 
8
- ### 0.13.1 / 2020-07-30
22
+ ## 0.13.1 / 2020-07-30
9
23
 
10
24
  * Support scopes when using GCE Metadata Server authentication ([@ball-hayden][])
11
25
 
12
- ### 0.13.0 / 2020-06-17
26
+ ## 0.13.0 / 2020-06-17
13
27
 
14
28
  * Support for validating ID tokens.
15
29
  * Fixed header application of ID tokens from service accounts.
16
30
 
17
- ### 0.12.0 / 2020-04-08
31
+ ## 0.12.0 / 2020-04-08
18
32
 
19
33
  * Support for ID token credentials.
20
34
  * Support reading quota_id_project from service account credentials.
21
35
 
22
- ### 0.11.0 / 2020-02-24
36
+ ## 0.11.0 / 2020-02-24
23
37
 
24
38
  * Support Faraday 1.x.
25
39
  * Allow special "postmessage" value for redirect_uri.
26
40
 
27
- ### 0.10.0 / 2019-10-09
41
+ ## 0.10.0 / 2019-10-09
28
42
 
29
43
  Note: This release now requires Ruby 2.4 or later
30
44
 
@@ -34,7 +48,7 @@ Note: This release now requires Ruby 2.4 or later
34
48
  * Set instance variables at initialization to avoid spamming warnings
35
49
  * Pass "Metadata-Flavor" header to metadata server when checking for GCE
36
50
 
37
- ### 0.9.0 / 2019-08-05
51
+ ## 0.9.0 / 2019-08-05
38
52
 
39
53
  * Restore compatibility with Ruby 2.0. This is the last release that will work on end-of-lifed versions of Ruby. The 0.10 release will require Ruby 2.4 or later.
40
54
  * Update Credentials to use methods for values that are intended to be changed by users, replacing constants.
@@ -43,79 +57,79 @@ Note: This release now requires Ruby 2.4 or later
43
57
  * Add verbosity none to gcloud command
44
58
  * Make arity of WebUserAuthorizer#get_credentials compatible with the base class
45
59
 
46
- ### 0.8.1 / 2019-03-27
60
+ ## 0.8.1 / 2019-03-27
47
61
 
48
62
  * Silence unnecessary gcloud warning
49
63
  * Treat empty credentials environment variables as unset
50
64
 
51
- ### 0.8.0 / 2019-01-02
65
+ ## 0.8.0 / 2019-01-02
52
66
 
53
67
  * Support connection options :default_connection and :connection_builder when creating credentials that need to refresh OAuth tokens. This lets clients provide connection objects with custom settings, such as proxies, needed for the client environment.
54
68
  * Removed an unnecessary warning about project IDs.
55
69
 
56
- ### 0.7.1 / 2018-10-25
70
+ ## 0.7.1 / 2018-10-25
57
71
 
58
72
  * Make load_gcloud_project_id module function.
59
73
 
60
- ### 0.7.0 / 2018-10-24
74
+ ## 0.7.0 / 2018-10-24
61
75
 
62
76
  * Add project_id instance variable to UserRefreshCredentials, ServiceAccountCredentials, and Credentials.
63
77
 
64
- ### 0.6.7 / 2018-10-16
78
+ ## 0.6.7 / 2018-10-16
65
79
 
66
80
  * Update memoist dependency to ~> 0.16.
67
81
 
68
- ### 0.6.6 / 2018-08-22
82
+ ## 0.6.6 / 2018-08-22
69
83
 
70
84
  * Remove ruby version warnings.
71
85
 
72
- ### 0.6.5 / 2018-08-16
86
+ ## 0.6.5 / 2018-08-16
73
87
 
74
88
  * Fix incorrect http verb when revoking credentials.
75
89
  * Warn on EOL ruby versions.
76
90
 
77
- ### 0.6.4 / 2018-08-03
91
+ ## 0.6.4 / 2018-08-03
78
92
 
79
93
  * Resolve issue where DefaultCredentials constant was undefined.
80
94
 
81
- ### 0.6.3 / 2018-08-02
95
+ ## 0.6.3 / 2018-08-02
82
96
 
83
97
  * Resolve issue where token_store was being written to twice
84
98
 
85
- ### 0.6.2 / 2018-08-01
99
+ ## 0.6.2 / 2018-08-01
86
100
 
87
101
  * Add warning when using cloud sdk credentials
88
102
 
89
- ### 0.6.1 / 2017-10-18
103
+ ## 0.6.1 / 2017-10-18
90
104
 
91
105
  * Fix file permissions
92
106
 
93
- ### 0.6.0 / 2017-10-17
107
+ ## 0.6.0 / 2017-10-17
94
108
 
95
109
  * Support ruby-jwt 2.0
96
110
  * Add simple credentials class
97
111
 
98
- ### 0.5.3 / 2017-07-21
112
+ ## 0.5.3 / 2017-07-21
99
113
 
100
114
  * Fix file permissions on the gem's `.rb` files.
101
115
 
102
- ### 0.5.2 / 2017-07-19
116
+ ## 0.5.2 / 2017-07-19
103
117
 
104
118
  * Add retry mechanism when fetching access tokens in `GCECredentials` and `UserRefreshCredentials` classes.
105
119
  * Update Google API OAuth2 token credential URI to v4.
106
120
 
107
- ### 0.5.1 / 2016-01-06
121
+ ## 0.5.1 / 2016-01-06
108
122
 
109
123
  * Change header name emitted by `Client#apply` from "Authorization" to "authorization" ([@murgatroid99][])
110
124
  * Fix ADC not working on some windows machines ([@vsubramani][])
111
125
  [#55](https://github.com/google/google-auth-library-ruby/issues/55)
112
126
 
113
- ### 0.5.0 / 2015-10-12
127
+ ## 0.5.0 / 2015-10-12
114
128
 
115
129
  * Initial support for user credentials ([@sqrrrl][])
116
130
  * Update Signet to 0.7
117
131
 
118
- ### 0.4.2 / 2015-08-05
132
+ ## 0.4.2 / 2015-08-05
119
133
 
120
134
  * Updated UserRefreshCredentials hash to use string keys ([@haabaato][])
121
135
  [#36](https://github.com/google/google-auth-library-ruby/issues/36)
@@ -132,16 +146,16 @@ Note: This release now requires Ruby 2.4 or later
132
146
  * Enables passing credentials via environment variables. ([@haabaato][])
133
147
  [#27](https://github.com/google/google-auth-library-ruby/issues/27)
134
148
 
135
- ### 0.4.1 / 2015-04-25
149
+ ## 0.4.1 / 2015-04-25
136
150
 
137
151
  * Improves handling of --no-scopes GCE authorization ([@tbetbetbe][])
138
152
  * Refactoring and cleanup ([@joneslee85][])
139
153
 
140
- ### 0.4.0 / 2015-03-25
154
+ ## 0.4.0 / 2015-03-25
141
155
 
142
156
  * Adds an implementation of JWT header auth ([@tbetbetbe][])
143
157
 
144
- ### 0.3.0 / 2015-03-23
158
+ ## 0.3.0 / 2015-03-23
145
159
 
146
160
  * makes the scope parameter's optional in all APIs. ([@tbetbetbe][])
147
161
  * changes the scope parameter's position in various constructors. ([@tbetbetbe][])
@@ -108,8 +108,7 @@ module Google
108
108
  uri = target_audience ? GCECredentials.compute_id_token_uri : GCECredentials.compute_auth_token_uri
109
109
  query = target_audience ? { "audience" => target_audience, "format" => "full" } : {}
110
110
  query[:scopes] = Array(scope).join "," if scope
111
- headers = { "Metadata-Flavor" => "Google" }
112
- resp = c.get uri, query, headers
111
+ resp = c.get uri, query, "Metadata-Flavor" => "Google"
113
112
  case resp.status
114
113
  when 200
115
114
  content_type = resp.headers["content-type"]
@@ -118,11 +117,13 @@ module Google
118
117
  else
119
118
  Signet::OAuth2.parse_credentials resp.body, content_type
120
119
  end
120
+ when 403, 500
121
+ msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
122
+ raise Signet::UnexpectedStatusError, msg
121
123
  when 404
122
124
  raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
123
125
  else
124
- msg = "Unexpected error code #{resp.status}" \
125
- "#{UNEXPECTED_ERROR_SUFFIX}"
126
+ msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
126
127
  raise Signet::AuthorizationError, msg
127
128
  end
128
129
  end
@@ -36,9 +36,46 @@ require "googleauth/credentials_loader"
36
36
  module Google
37
37
  module Auth
38
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
39
+ # Credentials is a high-level base class used by Google's API client
40
+ # libraries to represent the authentication when connecting to an API.
41
+ # In most cases, it is subclassed by API-specific credential classes that
42
+ # can be instantiated by clients.
43
+ #
44
+ # ## Options
45
+ #
46
+ # Credentials classes are configured with options that dictate default
47
+ # values for parameters such as scope and audience. These defaults are
48
+ # expressed as class attributes, and may differ from endpoint to endpoint.
49
+ # Normally, an API client will provide subclasses specific to each
50
+ # endpoint, configured with appropriate values.
51
+ #
52
+ # Note that these options inherit up the class hierarchy. If a particular
53
+ # options is not set for a subclass, its superclass is queried.
54
+ #
55
+ # Some older users of this class set options via constants. This usage is
56
+ # deprecated. For example, instead of setting the `AUDIENCE` constant on
57
+ # your subclass, call the `audience=` method.
58
+ #
59
+ # ## Example
60
+ #
61
+ # class MyCredentials < Google::Auth::Credentials
62
+ # # Set the default scope for these credentials
63
+ # self.scope = "http://example.com/my_scope"
64
+ # end
65
+ #
66
+ # # creds is a credentials object suitable for Google API clients
67
+ # creds = MyCredentials.default
68
+ # creds.scope # => ["http://example.com/my_scope"]
69
+ #
70
+ # class SubCredentials < MyCredentials
71
+ # # Override the default scope for this subclass
72
+ # self.scope = "http://example.com/sub_scope"
73
+ # end
74
+ #
75
+ # creds2 = SubCredentials.default
76
+ # creds2.scope # => ["http://example.com/sub_scope"]
77
+ #
78
+ class Credentials # rubocop:disable Metrics/ClassLength
42
79
  ##
43
80
  # The default token credential URI to be used when none is provided during initialization.
44
81
  TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token".freeze
@@ -47,7 +84,7 @@ module Google
47
84
  # The default target audience ID to be used when none is provided during initialization.
48
85
  AUDIENCE = "https://oauth2.googleapis.com/token".freeze
49
86
 
50
- @audience = @scope = @target_audience = @env_vars = @paths = nil
87
+ @audience = @scope = @target_audience = @env_vars = @paths = @token_credential_uri = nil
51
88
 
52
89
  ##
53
90
  # The default token credential URI to be used when none is provided during initialization.
@@ -57,9 +94,9 @@ module Google
57
94
  # @return [String]
58
95
  #
59
96
  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
97
+ lookup_auth_param :token_credential_uri do
98
+ lookup_local_constant :TOKEN_CREDENTIAL_URI
99
+ end
63
100
  end
64
101
 
65
102
  ##
@@ -79,9 +116,9 @@ module Google
79
116
  # @return [String]
80
117
  #
81
118
  def self.audience
82
- return @audience unless @audience.nil?
83
-
84
- const_get :AUDIENCE if const_defined? :AUDIENCE
119
+ lookup_auth_param :audience do
120
+ lookup_local_constant :AUDIENCE
121
+ end
85
122
  end
86
123
 
87
124
  ##
@@ -106,9 +143,10 @@ module Google
106
143
  # @return [String, Array<String>]
107
144
  #
108
145
  def self.scope
109
- return @scope unless @scope.nil?
110
-
111
- Array(const_get(:SCOPE)).flatten.uniq if const_defined? :SCOPE
146
+ lookup_auth_param :scope do
147
+ vals = lookup_local_constant :SCOPE
148
+ vals ? Array(vals).flatten.uniq : nil
149
+ end
112
150
  end
113
151
 
114
152
  ##
@@ -137,7 +175,7 @@ module Google
137
175
  # @return [String]
138
176
  #
139
177
  def self.target_audience
140
- @target_audience
178
+ lookup_auth_param :target_audience
141
179
  end
142
180
 
143
181
  ##
@@ -161,13 +199,12 @@ module Google
161
199
  # @return [Array<String>]
162
200
  #
163
201
  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
202
+ lookup_auth_param :env_vars do
203
+ # Pull values when PATH_ENV_VARS or JSON_ENV_VARS constants exists.
204
+ path_env_vars = lookup_local_constant :PATH_ENV_VARS
205
+ json_env_vars = lookup_local_constant :JSON_ENV_VARS
206
+ (Array(path_env_vars) + Array(json_env_vars)).flatten.uniq if path_env_vars || json_env_vars
207
+ end
171
208
  end
172
209
 
173
210
  ##
@@ -187,12 +224,11 @@ module Google
187
224
  # @return [Array<String>]
188
225
  #
189
226
  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
227
+ lookup_auth_param :paths do
228
+ # Pull in values if the DEFAULT_PATHS constant exists.
229
+ vals = lookup_local_constant :DEFAULT_PATHS
230
+ vals ? Array(vals).flatten.uniq : nil
231
+ end
196
232
  end
197
233
 
198
234
  ##
@@ -206,6 +242,39 @@ module Google
206
242
  @paths = new_paths
207
243
  end
208
244
 
245
+ ##
246
+ # @private
247
+ # Return the given parameter value, defaulting up the class hierarchy.
248
+ #
249
+ # First returns the value of the instance variable, if set.
250
+ # Next, calls the given block if provided. (This is generally used to
251
+ # look up legacy constant-based values.)
252
+ # Otherwise, calls the superclass method if present.
253
+ # Returns nil if all steps fail.
254
+ #
255
+ # @param [Symbol] The parameter name
256
+ # @return [Object] The value
257
+ #
258
+ def self.lookup_auth_param name
259
+ val = instance_variable_get "@#{name}".to_sym
260
+ val = yield if val.nil? && block_given?
261
+ return val unless val.nil?
262
+ return superclass.send name if superclass.respond_to? name
263
+ nil
264
+ end
265
+
266
+ ##
267
+ # @private
268
+ # Return the value of the given constant if it is defined directly in
269
+ # this class, or nil if not.
270
+ #
271
+ # @param [Symbol] Name of the constant
272
+ # @return [Object] The value
273
+ #
274
+ def self.lookup_local_constant name
275
+ const_defined?(name, false) ? const_get(name) : nil
276
+ end
277
+
209
278
  ##
210
279
  # The Signet::OAuth2::Client object the Credentials instance is using.
211
280
  #
@@ -336,8 +405,15 @@ module Google
336
405
  env_vars.each do |env_var|
337
406
  str = ENV[env_var]
338
407
  next if str.nil?
339
- return new str, options if ::File.file? str
340
- return new ::JSON.parse(str), options rescue nil
408
+ io =
409
+ if ::File.file? str
410
+ ::StringIO.new ::File.read str
411
+ else
412
+ json = ::JSON.parse str rescue nil
413
+ json ? ::StringIO.new(str) : nil
414
+ end
415
+ next if io.nil?
416
+ return from_io io, options
341
417
  end
342
418
  nil
343
419
  end
@@ -345,11 +421,11 @@ module Google
345
421
  ##
346
422
  # @private Lookup Credentials from default file paths.
347
423
  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
424
+ paths.each do |path|
425
+ next unless path && ::File.file?(path)
426
+ io = ::StringIO.new ::File.read path
427
+ return from_io io, options
428
+ end
353
429
  nil
354
430
  end
355
431
 
@@ -357,14 +433,34 @@ module Google
357
433
  # @private Lookup Credentials using Google::Auth.get_application_default.
358
434
  def self.from_application_default options
359
435
  scope = options[:scope] || self.scope
360
- auth_opts = { target_audience: options[:target_audience] || target_audience }
436
+ auth_opts = {
437
+ token_credential_uri: options[:token_credential_uri] || token_credential_uri,
438
+ audience: options[:audience] || audience,
439
+ target_audience: options[:target_audience] || target_audience,
440
+ enable_self_signed_jwt: options[:enable_self_signed_jwt] && options[:scope].nil?
441
+ }
361
442
  client = Google::Auth.get_application_default scope, auth_opts
362
443
  new client, options
363
444
  end
364
445
 
446
+ # @private Read credentials from a JSON stream.
447
+ def self.from_io io, options
448
+ creds_input = {
449
+ json_key_io: io,
450
+ scope: options[:scope] || scope,
451
+ target_audience: options[:target_audience] || target_audience,
452
+ enable_self_signed_jwt: options[:enable_self_signed_jwt] && options[:scope].nil?,
453
+ token_credential_uri: options[:token_credential_uri] || token_credential_uri,
454
+ audience: options[:audience] || audience
455
+ }
456
+ client = Google::Auth::DefaultCredentials.make_creds creds_input
457
+ new client
458
+ end
459
+
365
460
  private_class_method :from_env_vars,
366
461
  :from_default_paths,
367
- :from_application_default
462
+ :from_application_default,
463
+ :from_io
368
464
 
369
465
  protected
370
466
 
@@ -53,12 +53,18 @@ module Google
53
53
  attr_reader :project_id
54
54
  attr_reader :quota_project_id
55
55
 
56
+ def enable_self_signed_jwt?
57
+ @enable_self_signed_jwt
58
+ end
59
+
56
60
  # Creates a ServiceAccountCredentials.
57
61
  #
58
62
  # @param json_key_io [IO] an IO from which the JSON key can be read
59
63
  # @param scope [string|array|nil] the scope(s) to access
60
64
  def self.make_creds options = {}
61
- json_key_io, scope, target_audience = options.values_at :json_key_io, :scope, :target_audience
65
+ json_key_io, scope, enable_self_signed_jwt, target_audience, audience, token_credential_uri =
66
+ options.values_at :json_key_io, :scope, :enable_self_signed_jwt, :target_audience,
67
+ :audience, :token_credential_uri
62
68
  raise ArgumentError, "Cannot specify both scope and target_audience" if scope && target_audience
63
69
 
64
70
  if json_key_io
@@ -71,14 +77,15 @@ module Google
71
77
  end
72
78
  project_id ||= CredentialsLoader.load_gcloud_project_id
73
79
 
74
- new(token_credential_uri: TOKEN_CRED_URI,
75
- audience: TOKEN_CRED_URI,
76
- scope: scope,
77
- target_audience: target_audience,
78
- issuer: client_email,
79
- signing_key: OpenSSL::PKey::RSA.new(private_key),
80
- project_id: project_id,
81
- quota_project_id: quota_project_id)
80
+ new(token_credential_uri: token_credential_uri || TOKEN_CRED_URI,
81
+ audience: audience || TOKEN_CRED_URI,
82
+ scope: scope,
83
+ enable_self_signed_jwt: enable_self_signed_jwt,
84
+ target_audience: target_audience,
85
+ issuer: client_email,
86
+ signing_key: OpenSSL::PKey::RSA.new(private_key),
87
+ project_id: project_id,
88
+ quota_project_id: quota_project_id)
82
89
  .configure_connection(options)
83
90
  end
84
91
 
@@ -94,30 +101,33 @@ module Google
94
101
  def initialize options = {}
95
102
  @project_id = options[:project_id]
96
103
  @quota_project_id = options[:quota_project_id]
104
+ @enable_self_signed_jwt = options[:enable_self_signed_jwt] ? true : false
97
105
  super options
98
106
  end
99
107
 
100
- # Extends the base class.
101
- #
102
- # If scope(s) is not set, it creates a transient
103
- # ServiceAccountJwtHeaderCredentials instance and uses that to
104
- # authenticate instead.
108
+ # Extends the base class to use a transient
109
+ # ServiceAccountJwtHeaderCredentials for certain cases.
105
110
  def apply! a_hash, opts = {}
106
- # Use the base implementation if scopes are set
107
- unless scope.nil? && target_audience.nil?
111
+ # Use a self-singed JWT if there's no information that can be used to
112
+ # obtain an OAuth token, OR if there are scopes but also an assertion
113
+ # that they are default scopes that shouldn't be used to fetch a token.
114
+ if target_audience.nil? && (scope.nil? || enable_self_signed_jwt?)
115
+ apply_self_signed_jwt! a_hash
116
+ else
108
117
  super
109
- return
110
118
  end
119
+ end
120
+
121
+ private
111
122
 
123
+ def apply_self_signed_jwt! a_hash
112
124
  # Use the ServiceAccountJwtHeaderCredentials using the same cred values
113
- # if no scopes are set.
114
125
  cred_json = {
115
126
  private_key: @signing_key.to_s,
116
127
  client_email: @issuer
117
128
  }
118
- alt_clz = ServiceAccountJwtHeaderCredentials
119
129
  key_io = StringIO.new MultiJson.dump(cred_json)
120
- alt = alt_clz.make_creds json_key_io: key_io
130
+ alt = ServiceAccountJwtHeaderCredentials.make_creds json_key_io: key_io
121
131
  alt.apply! a_hash
122
132
  end
123
133
  end
@@ -31,6 +31,6 @@ module Google
31
31
  # Module Auth provides classes that provide Google-specific authorization
32
32
  # used to access Google APIs.
33
33
  module Auth
34
- VERSION = "0.14.0".freeze
34
+ VERSION = "0.15.0".freeze
35
35
  end
36
36
  end
@@ -58,12 +58,9 @@ module Google
58
58
  # end
59
59
  #
60
60
  # Instead of implementing the callback directly, applications are
61
- # encouraged to use {Google::Auth::Web::AuthCallbackApp} instead.
61
+ # encouraged to use {Google::Auth::WebUserAuthorizer::CallbackApp} instead.
62
62
  #
63
- # For rails apps, see {Google::Auth::ControllerHelpers}
64
- #
65
- # @see {Google::Auth::AuthCallbackApp}
66
- # @see {Google::Auth::ControllerHelpers}
63
+ # @see CallbackApp
67
64
  # @note Requires sessions are enabled
68
65
  class WebUserAuthorizer < Google::Auth::UserAuthorizer
69
66
  STATE_PARAM = "state".freeze
@@ -261,7 +258,7 @@ module Google
261
258
  # Google::Auth::WebUserAuthorizer::CallbackApp.call(env)
262
259
  # end
263
260
  #
264
- # @see {Google::Auth::WebUserAuthorizer}
261
+ # @see Google::Auth::WebUserAuthorizer
265
262
  class CallbackApp
266
263
  LOCATION_HEADER = "Location".freeze
267
264
  REDIR_STATUS = 302
@@ -90,6 +90,24 @@ describe Google::Auth::GCECredentials do
90
90
  expect(stub).to have_been_requested
91
91
  end
92
92
 
93
+ it "should fail if the metadata request returns a 403" do
94
+ stub = stub_request(:get, MD_ACCESS_URI)
95
+ .to_return(status: 403,
96
+ headers: { "Metadata-Flavor" => "Google" })
97
+ expect { @client.fetch_access_token! }
98
+ .to raise_error Signet::AuthorizationError
99
+ expect(stub).to have_been_requested.times(6)
100
+ end
101
+
102
+ it "should fail if the metadata request returns a 500" do
103
+ stub = stub_request(:get, MD_ACCESS_URI)
104
+ .to_return(status: 500,
105
+ headers: { "Metadata-Flavor" => "Google" })
106
+ expect { @client.fetch_access_token! }
107
+ .to raise_error Signet::AuthorizationError
108
+ expect(stub).to have_been_requested.times(6)
109
+ end
110
+
93
111
  it "should fail if the metadata request returns an unexpected code" do
94
112
  stub = stub_request(:get, MD_ACCESS_URI)
95
113
  .to_return(status: 503,
@@ -46,37 +46,37 @@ describe Google::Auth::Credentials, :private do
46
46
  }
47
47
  end
48
48
 
49
- it "uses a default scope" do
49
+ def mock_signet
50
50
  mocked_signet = double "Signet::OAuth2::Client"
51
51
  allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
52
52
  allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
53
53
  allow(mocked_signet).to receive(:client_id)
54
54
  allow(Signet::OAuth2::Client).to receive(:new) do |options|
55
+ yield options if block_given?
56
+ mocked_signet
57
+ end
58
+ mocked_signet
59
+ end
60
+
61
+ it "uses a default scope" do
62
+ mock_signet do |options|
55
63
  expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
56
64
  expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
57
65
  expect(options[:scope]).to eq([])
58
66
  expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
59
67
  expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
60
-
61
- mocked_signet
62
68
  end
63
69
 
64
70
  Google::Auth::Credentials.new default_keyfile_hash
65
71
  end
66
72
 
67
73
  it "uses a custom scope" do
68
- mocked_signet = double "Signet::OAuth2::Client"
69
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
70
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
71
- allow(mocked_signet).to receive(:client_id)
72
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
74
+ mock_signet do |options|
73
75
  expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
74
76
  expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
75
77
  expect(options[:scope]).to eq(["http://example.com/scope"])
76
78
  expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
77
79
  expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
78
-
79
- mocked_signet
80
80
  end
81
81
 
82
82
  Google::Auth::Credentials.new default_keyfile_hash, scope: "http://example.com/scope"
@@ -101,21 +101,22 @@ describe Google::Auth::Credentials, :private do
101
101
  allow(::File).to receive(:file?).with(test_path_env_val) { false }
102
102
  allow(::File).to receive(:file?).with(test_json_env_val) { false }
103
103
 
104
- mocked_signet = double "Signet::OAuth2::Client"
105
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
106
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
107
- allow(mocked_signet).to receive(:client_id)
108
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
104
+ mocked_signet = mock_signet
105
+
106
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
109
107
  expect(options[:token_credential_uri]).to eq("https://example.com/token")
110
108
  expect(options[:audience]).to eq("https://example.com/audience")
111
109
  expect(options[:scope]).to eq(["http://example.com/scope"])
112
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
113
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
110
+ expect(options[:enable_self_signed_jwt]).to eq(true)
111
+ expect(options[:target_audience]).to be_nil
112
+ expect(options[:json_key_io].read).to eq(test_json_env_val)
114
113
 
115
- mocked_signet
114
+ # This should really be a Signet::OAuth2::Client object,
115
+ # but mocking is making that difficult, so return a valid hash instead.
116
+ default_keyfile_hash
116
117
  end
117
118
 
118
- creds = TestCredentials1.default
119
+ creds = TestCredentials1.default enable_self_signed_jwt: true
119
120
  expect(creds).to be_a_kind_of(TestCredentials1)
120
121
  expect(creds.client).to eq(mocked_signet)
121
122
  expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
@@ -130,25 +131,28 @@ describe Google::Auth::Credentials, :private do
130
131
  DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
131
132
  end
132
133
 
134
+ json_content = JSON.generate default_keyfile_hash
135
+
133
136
  allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
134
137
  allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
135
138
  allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
136
139
  allow(::ENV).to receive(:[]).with("PATH_ENV_TEST") { "/unknown/path/to/file.txt" }
137
140
  allow(::File).to receive(:file?).with("/unknown/path/to/file.txt") { true }
138
- allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { JSON.generate default_keyfile_hash }
141
+ allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { json_content }
139
142
 
140
- mocked_signet = double "Signet::OAuth2::Client"
141
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
142
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
143
- allow(mocked_signet).to receive(:client_id)
144
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
143
+ mocked_signet = mock_signet
144
+
145
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
145
146
  expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
146
147
  expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
147
148
  expect(options[:scope]).to eq(["http://example.com/scope"])
148
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
149
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
149
+ expect(options[:enable_self_signed_jwt]).to be_nil
150
+ expect(options[:target_audience]).to be_nil
151
+ expect(options[:json_key_io].read).to eq(json_content)
150
152
 
151
- mocked_signet
153
+ # This should really be a Signet::OAuth2::Client object,
154
+ # but mocking is making that difficult, so return a valid hash instead.
155
+ default_keyfile_hash
152
156
  end
153
157
 
154
158
  creds = TestCredentials2.default
@@ -175,18 +179,19 @@ describe Google::Auth::Credentials, :private do
175
179
  allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
176
180
  allow(::ENV).to receive(:[]).with("JSON_ENV_TEST") { test_json_env_val }
177
181
 
178
- mocked_signet = double "Signet::OAuth2::Client"
179
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
180
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
181
- allow(mocked_signet).to receive(:client_id)
182
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
182
+ mocked_signet = mock_signet
183
+
184
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
183
185
  expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
184
186
  expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
185
187
  expect(options[:scope]).to eq(["http://example.com/scope"])
186
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
187
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
188
+ expect(options[:enable_self_signed_jwt]).to be_nil
189
+ expect(options[:target_audience]).to be_nil
190
+ expect(options[:json_key_io].read).to eq(test_json_env_val)
188
191
 
189
- mocked_signet
192
+ # This should really be a Signet::OAuth2::Client object,
193
+ # but mocking is making that difficult, so return a valid hash instead.
194
+ default_keyfile_hash
190
195
  end
191
196
 
192
197
  creds = TestCredentials3.default
@@ -204,25 +209,28 @@ describe Google::Auth::Credentials, :private do
204
209
  DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
205
210
  end
206
211
 
212
+ json_content = JSON.generate default_keyfile_hash
213
+
207
214
  allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
208
215
  allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
209
216
  allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
210
217
  allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
211
218
  allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { true }
212
- allow(::File).to receive(:read).with("~/default/path/to/file.txt") { JSON.generate default_keyfile_hash }
219
+ allow(::File).to receive(:read).with("~/default/path/to/file.txt") { json_content }
220
+
221
+ mocked_signet = mock_signet
213
222
 
214
- mocked_signet = double "Signet::OAuth2::Client"
215
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
216
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
217
- allow(mocked_signet).to receive(:client_id)
218
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
223
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
219
224
  expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
220
225
  expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
221
226
  expect(options[:scope]).to eq(["http://example.com/scope"])
222
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
223
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
227
+ expect(options[:enable_self_signed_jwt]).to be_nil
228
+ expect(options[:target_audience]).to be_nil
229
+ expect(options[:json_key_io].read).to eq(json_content)
224
230
 
225
- mocked_signet
231
+ # This should really be a Signet::OAuth2::Client object,
232
+ # but mocking is making that difficult, so return a valid hash instead.
233
+ default_keyfile_hash
226
234
  end
227
235
 
228
236
  creds = TestCredentials4.default
@@ -246,26 +254,18 @@ describe Google::Auth::Credentials, :private do
246
254
  allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
247
255
  allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
248
256
 
249
- mocked_signet = double "Signet::OAuth2::Client"
250
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
251
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
252
- allow(mocked_signet).to receive(:client_id)
253
- allow(Google::Auth).to receive(:get_application_default) do |scope|
257
+ mocked_signet = mock_signet
258
+
259
+ allow(Google::Auth).to receive(:get_application_default) do |scope, options|
254
260
  expect(scope).to eq([TestCredentials5::SCOPE])
261
+ expect(options[:enable_self_signed_jwt]).to be_nil
262
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
263
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
255
264
 
256
265
  # This should really be a Signet::OAuth2::Client object,
257
266
  # but mocking is making that difficult, so return a valid hash instead.
258
267
  default_keyfile_hash
259
268
  end
260
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
261
- expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
262
- expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
263
- expect(options[:scope]).to eq(["http://example.com/scope"])
264
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
265
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
266
-
267
- mocked_signet
268
- end
269
269
 
270
270
  creds = TestCredentials5.default
271
271
  expect(creds).to be_a_kind_of(TestCredentials5)
@@ -273,6 +273,31 @@ describe Google::Auth::Credentials, :private do
273
273
  expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
274
274
  expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
275
275
  end
276
+
277
+ it "can be subclassed to pass in other env paths" do
278
+ class TestCredentials6 < Google::Auth::Credentials
279
+ TOKEN_CREDENTIAL_URI = "https://example.com/token".freeze
280
+ AUDIENCE = "https://example.com/audience".freeze
281
+ SCOPE = "http://example.com/scope".freeze
282
+ PATH_ENV_VARS = ["TEST_PATH"].freeze
283
+ JSON_ENV_VARS = ["TEST_JSON_VARS"].freeze
284
+ DEFAULT_PATHS = ["~/default/path/to/file.txt"]
285
+ end
286
+
287
+ class TestCredentials7 < TestCredentials6
288
+ end
289
+
290
+ expect(TestCredentials7.token_credential_uri).to eq("https://example.com/token")
291
+ expect(TestCredentials7.audience).to eq("https://example.com/audience")
292
+ expect(TestCredentials7.scope).to eq(["http://example.com/scope"])
293
+ expect(TestCredentials7.env_vars).to eq(["TEST_PATH", "TEST_JSON_VARS"])
294
+ expect(TestCredentials7.paths).to eq(["~/default/path/to/file.txt"])
295
+
296
+ TestCredentials7::TOKEN_CREDENTIAL_URI = "https://example.com/token2"
297
+ expect(TestCredentials7.token_credential_uri).to eq("https://example.com/token2")
298
+ TestCredentials7::AUDIENCE = nil
299
+ expect(TestCredentials7.audience).to eq("https://example.com/audience")
300
+ end
276
301
  end
277
302
 
278
303
  describe "using class methods" do
@@ -293,18 +318,19 @@ describe Google::Auth::Credentials, :private do
293
318
  allow(::File).to receive(:file?).with(test_path_env_val) { false }
294
319
  allow(::File).to receive(:file?).with(test_json_env_val) { false }
295
320
 
296
- mocked_signet = double "Signet::OAuth2::Client"
297
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
298
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
299
- allow(mocked_signet).to receive(:client_id)
300
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
321
+ mocked_signet = mock_signet
322
+
323
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
301
324
  expect(options[:token_credential_uri]).to eq("https://example.com/token")
302
325
  expect(options[:audience]).to eq("https://example.com/audience")
303
326
  expect(options[:scope]).to eq(["http://example.com/scope"])
304
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
305
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
327
+ expect(options[:enable_self_signed_jwt]).to be_nil
328
+ expect(options[:target_audience]).to be_nil
329
+ expect(options[:json_key_io].read).to eq(test_json_env_val)
306
330
 
307
- mocked_signet
331
+ # This should really be a Signet::OAuth2::Client object,
332
+ # but mocking is making that difficult, so return a valid hash instead.
333
+ default_keyfile_hash
308
334
  end
309
335
 
310
336
  creds = TestCredentials11.default
@@ -321,25 +347,28 @@ describe Google::Auth::Credentials, :private do
321
347
  self.paths = ["~/default/path/to/file.txt"]
322
348
  end
323
349
 
350
+ json_content = JSON.generate default_keyfile_hash
351
+
324
352
  allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
325
353
  allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
326
354
  allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
327
355
  allow(::ENV).to receive(:[]).with("PATH_ENV_TEST") { "/unknown/path/to/file.txt" }
328
356
  allow(::File).to receive(:file?).with("/unknown/path/to/file.txt") { true }
329
- allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { JSON.generate default_keyfile_hash }
357
+ allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { json_content }
330
358
 
331
- mocked_signet = double "Signet::OAuth2::Client"
332
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
333
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
334
- allow(mocked_signet).to receive(:client_id)
335
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
359
+ mocked_signet = mock_signet
360
+
361
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
336
362
  expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
337
363
  expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
338
364
  expect(options[:scope]).to eq(["http://example.com/scope"])
339
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
340
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
365
+ expect(options[:enable_self_signed_jwt]).to be_nil
366
+ expect(options[:target_audience]).to be_nil
367
+ expect(options[:json_key_io].read).to eq(json_content)
341
368
 
342
- mocked_signet
369
+ # This should really be a Signet::OAuth2::Client object,
370
+ # but mocking is making that difficult, so return a valid hash instead.
371
+ default_keyfile_hash
343
372
  end
344
373
 
345
374
  creds = TestCredentials12.default
@@ -365,18 +394,19 @@ describe Google::Auth::Credentials, :private do
365
394
  allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
366
395
  allow(::ENV).to receive(:[]).with("JSON_ENV_TEST") { test_json_env_val }
367
396
 
368
- mocked_signet = double "Signet::OAuth2::Client"
369
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
370
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
371
- allow(mocked_signet).to receive(:client_id)
372
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
397
+ mocked_signet = mock_signet
398
+
399
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
373
400
  expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
374
401
  expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
375
402
  expect(options[:scope]).to eq(["http://example.com/scope"])
376
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
377
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
403
+ expect(options[:enable_self_signed_jwt]).to be_nil
404
+ expect(options[:target_audience]).to be_nil
405
+ expect(options[:json_key_io].read).to eq(test_json_env_val)
378
406
 
379
- mocked_signet
407
+ # This should really be a Signet::OAuth2::Client object,
408
+ # but mocking is making that difficult, so return a valid hash instead.
409
+ default_keyfile_hash
380
410
  end
381
411
 
382
412
  creds = TestCredentials13.default
@@ -393,25 +423,28 @@ describe Google::Auth::Credentials, :private do
393
423
  self.paths = ["~/default/path/to/file.txt"]
394
424
  end
395
425
 
426
+ json_content = JSON.generate default_keyfile_hash
427
+
396
428
  allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
397
429
  allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
398
430
  allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
399
431
  allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
400
432
  allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { true }
401
- allow(::File).to receive(:read).with("~/default/path/to/file.txt") { JSON.generate default_keyfile_hash }
433
+ allow(::File).to receive(:read).with("~/default/path/to/file.txt") { json_content }
434
+
435
+ mocked_signet = mock_signet
402
436
 
403
- mocked_signet = double "Signet::OAuth2::Client"
404
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
405
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
406
- allow(mocked_signet).to receive(:client_id)
407
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
437
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
408
438
  expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
409
439
  expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
410
440
  expect(options[:scope]).to eq(["http://example.com/scope"])
411
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
412
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
441
+ expect(options[:enable_self_signed_jwt]).to be_nil
442
+ expect(options[:target_audience]).to be_nil
443
+ expect(options[:json_key_io].read).to eq(json_content)
413
444
 
414
- mocked_signet
445
+ # This should really be a Signet::OAuth2::Client object,
446
+ # but mocking is making that difficult, so return a valid hash instead.
447
+ default_keyfile_hash
415
448
  end
416
449
 
417
450
  creds = TestCredentials14.default
@@ -421,7 +454,7 @@ describe Google::Auth::Credentials, :private do
421
454
  expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
422
455
  end
423
456
 
424
- it "subclasses that find no matches default to Google::Auth.get_application_default" do
457
+ it "subclasses that find no matches default to Google::Auth.get_application_default with self-signed jwt enabled" do
425
458
  class TestCredentials15 < Google::Auth::Credentials
426
459
  self.scope = "http://example.com/scope"
427
460
  self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY]
@@ -434,33 +467,117 @@ describe Google::Auth::Credentials, :private do
434
467
  allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
435
468
  allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
436
469
 
437
- mocked_signet = double "Signet::OAuth2::Client"
438
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
439
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
440
- allow(mocked_signet).to receive(:client_id)
441
- allow(Google::Auth).to receive(:get_application_default) do |scope|
470
+ mocked_signet = mock_signet
471
+
472
+ allow(Google::Auth).to receive(:get_application_default) do |scope, options|
442
473
  expect(scope).to eq(TestCredentials15.scope)
474
+ expect(options[:enable_self_signed_jwt]).to eq(true)
475
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
476
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
443
477
 
444
478
  # This should really be a Signet::OAuth2::Client object,
445
479
  # but mocking is making that difficult, so return a valid hash instead.
446
480
  default_keyfile_hash
447
481
  end
448
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
482
+
483
+ creds = TestCredentials15.default enable_self_signed_jwt: true
484
+ expect(creds).to be_a_kind_of(TestCredentials15)
485
+ expect(creds.client).to eq(mocked_signet)
486
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
487
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
488
+ end
489
+
490
+ it "subclasses that find no matches default to Google::Auth.get_application_default with self-signed jwt disabled" do
491
+ class TestCredentials16 < Google::Auth::Credentials
492
+ self.scope = "http://example.com/scope"
493
+ self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY]
494
+ self.paths = ["~/default/path/to/file.txt"]
495
+ end
496
+
497
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
498
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
499
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
500
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
501
+ allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
502
+
503
+ mocked_signet = mock_signet
504
+
505
+ allow(Google::Auth).to receive(:get_application_default) do |scope, options|
506
+ expect(scope).to eq(TestCredentials16.scope)
507
+ expect(options[:enable_self_signed_jwt]).to be_nil
449
508
  expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
450
509
  expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
451
- expect(options[:scope]).to eq(["http://example.com/scope"])
452
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
453
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
454
510
 
455
- mocked_signet
511
+ # This should really be a Signet::OAuth2::Client object,
512
+ # but mocking is making that difficult, so return a valid hash instead.
513
+ default_keyfile_hash
456
514
  end
457
515
 
458
- creds = TestCredentials15.default
459
- expect(creds).to be_a_kind_of(TestCredentials15)
516
+ creds = TestCredentials16.default
517
+ expect(creds).to be_a_kind_of(TestCredentials16)
460
518
  expect(creds.client).to eq(mocked_signet)
461
519
  expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
462
520
  expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
463
521
  end
522
+
523
+ it "subclasses that find no matches default to Google::Auth.get_application_default with custom values" do
524
+ scope2 = "http://example.com/scope2"
525
+
526
+ class TestCredentials17 < Google::Auth::Credentials
527
+ self.scope = "http://example.com/scope"
528
+ self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY]
529
+ self.paths = ["~/default/path/to/file.txt"]
530
+ self.token_credential_uri = "https://example.com/token2"
531
+ self.audience = "https://example.com/token3"
532
+ end
533
+
534
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
535
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
536
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
537
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
538
+ allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
539
+
540
+ mocked_signet = mock_signet
541
+
542
+ allow(Google::Auth).to receive(:get_application_default) do |scope, options|
543
+ expect(scope).to eq(scope2)
544
+ expect(options[:enable_self_signed_jwt]).to eq(false)
545
+ expect(options[:token_credential_uri]).to eq("https://example.com/token2")
546
+ expect(options[:audience]).to eq("https://example.com/token3")
547
+
548
+ # This should really be a Signet::OAuth2::Client object,
549
+ # but mocking is making that difficult, so return a valid hash instead.
550
+ default_keyfile_hash
551
+ end
552
+
553
+ creds = TestCredentials17.default scope: scope2, enable_self_signed_jwt: true
554
+ expect(creds).to be_a_kind_of(TestCredentials17)
555
+ expect(creds.client).to eq(mocked_signet)
556
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
557
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
558
+ end
559
+
560
+ it "subclasses delegate up the class hierarchy" do
561
+ class TestCredentials18 < Google::Auth::Credentials
562
+ self.scope = "http://example.com/scope"
563
+ self.target_audience = "https://example.com/target_audience"
564
+ self.env_vars = ["TEST_PATH", "TEST_JSON_VARS"]
565
+ self.paths = ["~/default/path/to/file.txt"]
566
+ end
567
+
568
+ class TestCredentials19 < TestCredentials18
569
+ end
570
+
571
+ expect(TestCredentials19.scope).to eq(["http://example.com/scope"])
572
+ expect(TestCredentials19.target_audience).to eq("https://example.com/target_audience")
573
+ expect(TestCredentials19.env_vars).to eq(["TEST_PATH", "TEST_JSON_VARS"])
574
+ expect(TestCredentials19.paths).to eq(["~/default/path/to/file.txt"])
575
+
576
+ TestCredentials19.token_credential_uri = "https://example.com/token2"
577
+ expect(TestCredentials19.token_credential_uri).to eq("https://example.com/token2")
578
+ TestCredentials19.token_credential_uri = nil
579
+ expect(TestCredentials19.token_credential_uri).to eq("https://oauth2.googleapis.com/token")
580
+ end
464
581
  end
465
582
 
466
583
  it "warns when cloud sdk credentials are used" do
@@ -169,6 +169,14 @@ describe Google::Auth::ServiceAccountCredentials do
169
169
  it_behaves_like "jwt header auth"
170
170
  end
171
171
 
172
+ context "when enable_self_signed_jwt is set" do
173
+ before :example do
174
+ @client.instance_variable_set(:@enable_self_signed_jwt, true)
175
+ end
176
+
177
+ it_behaves_like "jwt header auth"
178
+ end
179
+
172
180
  describe "#from_env" do
173
181
  before :example do
174
182
  @var_name = ENV_VAR
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: googleauth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Emiola
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-09 00:00:00.000000000 Z
11
+ date: 2021-01-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -140,6 +140,7 @@ files:
140
140
  - ".github/ISSUE_TEMPLATE/bug_report.md"
141
141
  - ".github/ISSUE_TEMPLATE/feature_request.md"
142
142
  - ".github/ISSUE_TEMPLATE/support_request.md"
143
+ - ".github/workflows/release.yml"
143
144
  - ".gitignore"
144
145
  - ".kokoro/build.bat"
145
146
  - ".kokoro/build.sh"
@@ -232,7 +233,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
232
233
  - !ruby/object:Gem::Version
233
234
  version: '0'
234
235
  requirements: []
235
- rubygems_version: 3.1.4
236
+ rubygems_version: 3.2.6
236
237
  signing_key:
237
238
  specification_version: 4
238
239
  summary: Google Auth Library for Ruby