googleauth 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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