googleauth 1.8.0 → 1.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +117 -0
  3. data/Credentials.md +106 -0
  4. data/Errors.md +152 -0
  5. data/README.md +49 -1
  6. data/lib/googleauth/api_key.rb +164 -0
  7. data/lib/googleauth/application_default.rb +6 -8
  8. data/lib/googleauth/base_client.rb +21 -4
  9. data/lib/googleauth/bearer_token.rb +162 -0
  10. data/lib/googleauth/client_id.rb +9 -6
  11. data/lib/googleauth/compute_engine.rb +231 -49
  12. data/lib/googleauth/credentials.rb +187 -58
  13. data/lib/googleauth/credentials_loader.rb +11 -20
  14. data/lib/googleauth/default_credentials.rb +29 -8
  15. data/lib/googleauth/errors.rb +117 -0
  16. data/lib/googleauth/external_account/aws_credentials.rb +85 -18
  17. data/lib/googleauth/external_account/base_credentials.rb +67 -6
  18. data/lib/googleauth/external_account/external_account_utils.rb +15 -4
  19. data/lib/googleauth/external_account/identity_pool_credentials.rb +40 -15
  20. data/lib/googleauth/external_account/pluggable_credentials.rb +34 -19
  21. data/lib/googleauth/external_account.rb +32 -7
  22. data/lib/googleauth/helpers/connection.rb +7 -1
  23. data/lib/googleauth/iam.rb +19 -3
  24. data/lib/googleauth/id_tokens/errors.rb +13 -7
  25. data/lib/googleauth/id_tokens/key_sources.rb +13 -7
  26. data/lib/googleauth/id_tokens/verifier.rb +2 -3
  27. data/lib/googleauth/id_tokens.rb +4 -6
  28. data/lib/googleauth/impersonated_service_account.rb +329 -0
  29. data/lib/googleauth/json_key_reader.rb +13 -3
  30. data/lib/googleauth/oauth2/sts_client.rb +9 -4
  31. data/lib/googleauth/scope_util.rb +1 -1
  32. data/lib/googleauth/service_account.rb +84 -104
  33. data/lib/googleauth/service_account_jwt_header.rb +187 -0
  34. data/lib/googleauth/signet.rb +169 -4
  35. data/lib/googleauth/token_store.rb +3 -3
  36. data/lib/googleauth/user_authorizer.rb +89 -11
  37. data/lib/googleauth/user_refresh.rb +72 -9
  38. data/lib/googleauth/version.rb +1 -1
  39. data/lib/googleauth/web_user_authorizer.rb +65 -17
  40. data/lib/googleauth.rb +8 -0
  41. metadata +45 -13
@@ -12,8 +12,11 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ require "base64"
16
+ require "json"
15
17
  require "signet/oauth_2/client"
16
18
  require "googleauth/base_client"
19
+ require "googleauth/errors"
17
20
 
18
21
  module Signet
19
22
  # OAuth2 supports OAuth2 authentication.
@@ -25,6 +28,33 @@ module Signet
25
28
  class Client
26
29
  include Google::Auth::BaseClient
27
30
 
31
+ alias update_token_signet_base update_token!
32
+
33
+ def update_token! options = {}
34
+ options = deep_hash_normalize options
35
+ id_token_expires_at = expires_at_from_id_token options[:id_token]
36
+ options[:expires_at] = id_token_expires_at if id_token_expires_at
37
+ update_token_signet_base options
38
+ self.universe_domain = options[:universe_domain] if options.key? :universe_domain
39
+ self
40
+ end
41
+
42
+ alias update_signet_base update!
43
+ def update! options = {}
44
+ # Normalize all keys to symbols to allow indifferent access.
45
+ options = deep_hash_normalize options
46
+
47
+ # This `update!` method "overide" adds the `@logger`` update and
48
+ # the `universe_domain` update.
49
+ #
50
+ # The `universe_domain` is also updated in `update_token!` but is
51
+ # included here for completeness
52
+ self.universe_domain = options[:universe_domain] if options.key? :universe_domain
53
+ @logger = options[:logger] if options.key? :logger
54
+
55
+ update_signet_base options
56
+ end
57
+
28
58
  def configure_connection options
29
59
  @connection_info =
30
60
  options[:connection_builder] || options[:default_connection]
@@ -36,6 +66,9 @@ module Signet
36
66
  target_audience ? :id_token : :access_token
37
67
  end
38
68
 
69
+ # Set the universe domain
70
+ attr_accessor :universe_domain
71
+
39
72
  alias orig_fetch_access_token! fetch_access_token!
40
73
  def fetch_access_token! options = {}
41
74
  unless options[:connection]
@@ -49,6 +82,24 @@ module Signet
49
82
  info
50
83
  end
51
84
 
85
+ alias googleauth_orig_generate_access_token_request generate_access_token_request
86
+ def generate_access_token_request options = {}
87
+ parameters = googleauth_orig_generate_access_token_request options
88
+ logger&.info do
89
+ Google::Logging::Message.from(
90
+ message: "Requesting access token from #{parameters['grant_type']}",
91
+ "credentialsId" => object_id
92
+ )
93
+ end
94
+ logger&.debug do
95
+ Google::Logging::Message.from(
96
+ message: "Token fetch params: #{parameters}",
97
+ "credentialsId" => object_id
98
+ )
99
+ end
100
+ parameters
101
+ end
102
+
52
103
  def build_default_connection
53
104
  if !defined?(@connection_info)
54
105
  nil
@@ -59,24 +110,138 @@ module Signet
59
110
  end
60
111
  end
61
112
 
113
+ # rubocop:disable Metrics/MethodLength
114
+
115
+ # Retries the provided block with exponential backoff, handling and wrapping errors.
116
+ #
117
+ # @param [Integer] max_retry_count The maximum number of retries before giving up
118
+ # @yield The block to execute and potentially retry
119
+ # @return [Object] The result of the block if successful
120
+ # @raise [Google::Auth::AuthorizationError] If a Signet::AuthorizationError occurs or if retries are exhausted
121
+ # @raise [Google::Auth::ParseError] If a Signet::ParseError occurs during token parsing
62
122
  def retry_with_error max_retry_count = 5
63
123
  retry_count = 0
64
124
 
65
125
  begin
66
- yield
126
+ yield.tap { |resp| log_response resp }
127
+ rescue Signet::AuthorizationError, Signet::ParseError => e
128
+ log_auth_error e
129
+ error_class = e.is_a?(Signet::ParseError) ? Google::Auth::ParseError : Google::Auth::AuthorizationError
130
+ raise error_class.with_details(
131
+ e.message,
132
+ credential_type_name: self.class.name,
133
+ principal: respond_to?(:principal) ? principal : :signet_client
134
+ )
67
135
  rescue StandardError => e
68
- raise e if e.is_a?(Signet::AuthorizationError) || e.is_a?(Signet::ParseError)
69
-
70
136
  if retry_count < max_retry_count
137
+ log_transient_error e
71
138
  retry_count += 1
72
139
  sleep retry_count * 0.3
73
140
  retry
74
141
  else
142
+ log_retries_exhausted e
75
143
  msg = "Unexpected error: #{e.inspect}"
76
- raise Signet::AuthorizationError, msg
144
+ raise Google::Auth::AuthorizationError.with_details(
145
+ msg,
146
+ credential_type_name: self.class.name,
147
+ principal: respond_to?(:principal) ? principal : :signet_client
148
+ )
77
149
  end
78
150
  end
79
151
  end
152
+ # rubocop:enable Metrics/MethodLength
153
+
154
+ # Creates a duplicate of these credentials
155
+ # without the Signet::OAuth2::Client-specific
156
+ # transient state (e.g. cached tokens)
157
+ #
158
+ # @param options [Hash] Overrides for the credentials parameters.
159
+ # @see Signet::OAuth2::Client#update!
160
+ def duplicate options = {}
161
+ options = deep_hash_normalize options
162
+
163
+ opts = {
164
+ authorization_uri: @authorization_uri,
165
+ token_credential_uri: @token_credential_uri,
166
+ client_id: @client_id,
167
+ client_secret: @client_secret,
168
+ scope: @scope,
169
+ target_audience: @target_audience,
170
+ redirect_uri: @redirect_uri,
171
+ username: @username,
172
+ password: @password,
173
+ issuer: @issuer,
174
+ person: @person,
175
+ sub: @sub,
176
+ audience: @audience,
177
+ signing_key: @signing_key,
178
+ extension_parameters: @extension_parameters,
179
+ additional_parameters: @additional_parameters,
180
+ access_type: @access_type,
181
+ universe_domain: @universe_domain,
182
+ logger: @logger
183
+ }.merge(options)
184
+
185
+ new_client = self.class.new opts
186
+
187
+ new_client.configure_connection options
188
+ end
189
+
190
+ private
191
+
192
+ def expires_at_from_id_token id_token
193
+ match = /^[\w=-]+\.([\w=-]+)\.[\w=-]+$/.match id_token.to_s
194
+ return unless match
195
+ json = JSON.parse Base64.urlsafe_decode64 match[1]
196
+ return unless json.key? "exp"
197
+ Time.at json["exp"].to_i
198
+ rescue StandardError
199
+ # Shouldn't happen unless we get a garbled ID token
200
+ nil
201
+ end
202
+
203
+ def log_response token_response
204
+ response_hash = JSON.parse token_response rescue {}
205
+ if response_hash["access_token"]
206
+ digest = Digest::SHA256.hexdigest response_hash["access_token"]
207
+ response_hash["access_token"] = "(sha256:#{digest})"
208
+ end
209
+ if response_hash["id_token"]
210
+ digest = Digest::SHA256.hexdigest response_hash["id_token"]
211
+ response_hash["id_token"] = "(sha256:#{digest})"
212
+ end
213
+ Google::Logging::Message.from(
214
+ message: "Received auth token response: #{response_hash}",
215
+ "credentialsId" => object_id
216
+ )
217
+ end
218
+
219
+ def log_auth_error err
220
+ logger&.info do
221
+ Google::Logging::Message.from(
222
+ message: "Auth error when fetching auth token: #{err}",
223
+ "credentialsId" => object_id
224
+ )
225
+ end
226
+ end
227
+
228
+ def log_transient_error err
229
+ logger&.info do
230
+ Google::Logging::Message.from(
231
+ message: "Transient error when fetching auth token: #{err}",
232
+ "credentialsId" => object_id
233
+ )
234
+ end
235
+ end
236
+
237
+ def log_retries_exhausted err
238
+ logger&.info do
239
+ Google::Logging::Message.from(
240
+ message: "Exhausted retries when fetching auth token: #{err}",
241
+ "credentialsId" => object_id
242
+ )
243
+ end
244
+ end
80
245
  end
81
246
  end
82
247
  end
@@ -29,7 +29,7 @@ module Google
29
29
  # @return [String]
30
30
  # The loaded token data.
31
31
  def load _id
32
- raise "Not implemented"
32
+ raise NoMethodError, "load not implemented"
33
33
  end
34
34
 
35
35
  # Put the token data into storage for the given ID.
@@ -39,7 +39,7 @@ module Google
39
39
  # @param [String] token
40
40
  # The token data to store.
41
41
  def store _id, _token
42
- raise "Not implemented"
42
+ raise NoMethodError, "store not implemented"
43
43
  end
44
44
 
45
45
  # Remove the token data from storage for the given ID.
@@ -47,7 +47,7 @@ module Google
47
47
  # @param [String] id
48
48
  # ID of the token data to delete
49
49
  def delete _id
50
- raise "Not implemented"
50
+ raise NoMethodError, "delete not implemented"
51
51
  end
52
52
  end
53
53
  end
@@ -16,6 +16,7 @@ require "uri"
16
16
  require "multi_json"
17
17
  require "googleauth/signet"
18
18
  require "googleauth/user_refresh"
19
+ require "securerandom"
19
20
 
20
21
  module Google
21
22
  module Auth
@@ -54,17 +55,28 @@ module Google
54
55
  # Authorization scope to request
55
56
  # @param [Google::Auth::Stores::TokenStore] token_store
56
57
  # Backing storage for persisting user credentials
57
- # @param [String] callback_uri
58
+ # @param [String] legacy_callback_uri
58
59
  # URL (either absolute or relative) of the auth callback.
59
- # Defaults to '/oauth2callback'
60
- def initialize client_id, scope, token_store, callback_uri = nil
61
- raise NIL_CLIENT_ID_ERROR if client_id.nil?
62
- raise NIL_SCOPE_ERROR if scope.nil?
60
+ # Defaults to '/oauth2callback'.
61
+ # @deprecated This field is deprecated. Instead, use the keyword
62
+ # argument callback_uri.
63
+ # @param [String] code_verifier
64
+ # Random string of 43-128 chars used to verify the key exchange using
65
+ # PKCE.
66
+ # @raise [Google::Auth::InitializationError]
67
+ # If client_id is nil or scope is nil
68
+ def initialize client_id, scope, token_store,
69
+ legacy_callback_uri = nil,
70
+ callback_uri: nil,
71
+ code_verifier: nil
72
+ raise InitializationError, NIL_CLIENT_ID_ERROR if client_id.nil?
73
+ raise InitializationError, NIL_SCOPE_ERROR if scope.nil?
63
74
 
64
75
  @client_id = client_id
65
76
  @scope = Array(scope)
66
77
  @token_store = token_store
67
- @callback_uri = callback_uri || "/oauth2callback"
78
+ @callback_uri = legacy_callback_uri || callback_uri || "/oauth2callback"
79
+ @code_verifier = code_verifier
68
80
  end
69
81
 
70
82
  # Build the URL for requesting authorization.
@@ -86,6 +98,18 @@ module Google
86
98
  # Authorization url
87
99
  def get_authorization_url options = {}
88
100
  scope = options[:scope] || @scope
101
+
102
+ options[:additional_parameters] ||= {}
103
+
104
+ if @code_verifier
105
+ options[:additional_parameters].merge!(
106
+ {
107
+ code_challenge: generate_code_challenge(@code_verifier),
108
+ code_challenge_method: code_challenge_method
109
+ }
110
+ )
111
+ end
112
+
89
113
  credentials = UserRefreshCredentials.new(
90
114
  client_id: @client_id.id,
91
115
  client_secret: @client_id.secret,
@@ -111,14 +135,19 @@ module Google
111
135
  # the requested scopes
112
136
  # @return [Google::Auth::UserRefreshCredentials]
113
137
  # Stored credentials, nil if none present
138
+ # @raise [Google::Auth::CredentialsError]
139
+ # If the client ID in the stored token doesn't match the configured client ID
114
140
  def get_credentials user_id, scope = nil
115
141
  saved_token = stored_token user_id
116
142
  return nil if saved_token.nil?
117
143
  data = MultiJson.load saved_token
118
144
 
119
145
  if data.fetch("client_id", @client_id.id) != @client_id.id
120
- raise format(MISMATCHED_CLIENT_ID_ERROR,
121
- data["client_id"], @client_id.id)
146
+ raise CredentialsError.with_details(
147
+ format(MISMATCHED_CLIENT_ID_ERROR, data["client_id"], @client_id.id),
148
+ credential_type_name: self.class.name,
149
+ principal: principal
150
+ )
122
151
  end
123
152
 
124
153
  credentials = UserRefreshCredentials.new(
@@ -157,6 +186,8 @@ module Google
157
186
  code = options[:code]
158
187
  scope = options[:scope] || @scope
159
188
  base_url = options[:base_url]
189
+ options[:additional_parameters] ||= {}
190
+ options[:additional_parameters].merge!({ code_verifier: @code_verifier })
160
191
  credentials = UserRefreshCredentials.new(
161
192
  client_id: @client_id.id,
162
193
  client_secret: @client_id.secret,
@@ -216,6 +247,8 @@ module Google
216
247
  # Unique ID of the user for loading/storing credentials.
217
248
  # @param [Google::Auth::UserRefreshCredentials] credentials
218
249
  # Credentials to store.
250
+ # @return [Google::Auth::UserRefreshCredentials]
251
+ # The stored credentials
219
252
  def store_credentials user_id, credentials
220
253
  json = MultiJson.dump(
221
254
  client_id: credentials.client_id,
@@ -228,6 +261,32 @@ module Google
228
261
  credentials
229
262
  end
230
263
 
264
+ # The code verifier for PKCE for OAuth 2.0. When set, the
265
+ # authorization URI will contain the Code Challenge and Code
266
+ # Challenge Method querystring parameters, and the token URI will
267
+ # contain the Code Verifier parameter.
268
+ #
269
+ # @param [String|nil] new_code_erifier
270
+ def code_verifier= new_code_verifier
271
+ @code_verifier = new_code_verifier
272
+ end
273
+
274
+ # Generate the code verifier needed to be sent while fetching
275
+ # authorization URL.
276
+ def self.generate_code_verifier
277
+ random_number = rand 32..96
278
+ SecureRandom.alphanumeric random_number
279
+ end
280
+
281
+ # Returns the principal identifier for this authorizer
282
+ # The client ID is used as the principal for user authorizers
283
+ #
284
+ # @private
285
+ # @return [String] The client ID associated with this authorizer
286
+ def principal
287
+ @client_id.id
288
+ end
289
+
231
290
  private
232
291
 
233
292
  # @private Fetch stored token with given user_id
@@ -235,9 +294,11 @@ module Google
235
294
  # @param [String] user_id
236
295
  # Unique ID of the user for loading/storing credentials.
237
296
  # @return [String] The saved token from @token_store
297
+ # @raise [Google::Auth::InitializationError]
298
+ # If user_id is nil or token_store is nil
238
299
  def stored_token user_id
239
- raise NIL_USER_ID_ERROR if user_id.nil?
240
- raise NIL_TOKEN_STORE_ERROR if @token_store.nil?
300
+ raise InitializationError, NIL_USER_ID_ERROR if user_id.nil?
301
+ raise InitializationError, NIL_TOKEN_STORE_ERROR if @token_store.nil?
241
302
 
242
303
  @token_store.load user_id
243
304
  end
@@ -262,9 +323,17 @@ module Google
262
323
  # Absolute URL to resolve the callback against if necessary.
263
324
  # @return [String]
264
325
  # Redirect URI
326
+ # @raise [Google::Auth::CredentialsError]
327
+ # If the callback URI is relative and base_url is nil or not absolute
265
328
  def redirect_uri_for base_url
266
329
  return @callback_uri if uri_is_postmessage?(@callback_uri) || !URI(@callback_uri).scheme.nil?
267
- raise format(MISSING_ABSOLUTE_URL_ERROR, @callback_uri) if base_url.nil? || URI(base_url).scheme.nil?
330
+ if base_url.nil? || URI(base_url).scheme.nil?
331
+ raise CredentialsError.with_details(
332
+ format(MISSING_ABSOLUTE_URL_ERROR, @callback_uri),
333
+ credential_type_name: self.class.name,
334
+ principal: principal
335
+ )
336
+ end
268
337
  URI.join(base_url, @callback_uri).to_s
269
338
  end
270
339
 
@@ -272,6 +341,15 @@ module Google
272
341
  def uri_is_postmessage? uri
273
342
  uri.to_s.casecmp("postmessage").zero?
274
343
  end
344
+
345
+ def generate_code_challenge code_verifier
346
+ digest = Digest::SHA256.digest code_verifier
347
+ Base64.urlsafe_encode64 digest, padding: false
348
+ end
349
+
350
+ def code_challenge_method
351
+ "S256"
352
+ end
275
353
  end
276
354
  end
277
355
  end
@@ -12,9 +12,10 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- require "googleauth/signet"
16
15
  require "googleauth/credentials_loader"
16
+ require "googleauth/errors"
17
17
  require "googleauth/scope_util"
18
+ require "googleauth/signet"
18
19
  require "multi_json"
19
20
 
20
21
  module Google
@@ -40,7 +41,7 @@ module Google
40
41
 
41
42
  # Create a UserRefreshCredentials.
42
43
  #
43
- # @param json_key_io [IO] an IO from which the JSON key can be read
44
+ # @param json_key_io [IO] An IO object containing the JSON key
44
45
  # @param scope [string|array|nil] the scope(s) to access
45
46
  def self.make_creds options = {}
46
47
  json_key_io, scope = options.values_at :json_key_io, :scope
@@ -50,7 +51,8 @@ module Google
50
51
  "client_secret" => ENV[CredentialsLoader::CLIENT_SECRET_VAR],
51
52
  "refresh_token" => ENV[CredentialsLoader::REFRESH_TOKEN_VAR],
52
53
  "project_id" => ENV[CredentialsLoader::PROJECT_ID_VAR],
53
- "quota_project_id" => nil
54
+ "quota_project_id" => nil,
55
+ "universe_domain" => nil
54
56
  }
55
57
  new(token_credential_uri: TOKEN_CRED_URI,
56
58
  client_id: user_creds["client_id"],
@@ -58,17 +60,21 @@ module Google
58
60
  refresh_token: user_creds["refresh_token"],
59
61
  project_id: user_creds["project_id"],
60
62
  quota_project_id: user_creds["quota_project_id"],
61
- scope: scope)
63
+ scope: scope,
64
+ universe_domain: user_creds["universe_domain"] || "googleapis.com")
62
65
  .configure_connection(options)
63
66
  end
64
67
 
65
- # Reads the client_id, client_secret and refresh_token fields from the
66
- # JSON key.
68
+ # Reads a JSON key from an IO object and extracts required fields.
69
+ #
70
+ # @param [IO] json_key_io An IO object containing the JSON key
71
+ # @return [Hash] The parsed JSON key
72
+ # @raise [Google::Auth::InitializationError] If the JSON is missing required fields
67
73
  def self.read_json_key json_key_io
68
74
  json_key = MultiJson.load json_key_io.read
69
75
  wanted = ["client_id", "client_secret", "refresh_token"]
70
76
  wanted.each do |key|
71
- raise "the json is missing the #{key} field" unless json_key.key? key
77
+ raise InitializationError, "the json is missing the #{key} field" unless json_key.key? key
72
78
  end
73
79
  json_key
74
80
  end
@@ -83,7 +89,31 @@ module Google
83
89
  super options
84
90
  end
85
91
 
92
+ # Creates a duplicate of these credentials
93
+ # without the Signet::OAuth2::Client-specific
94
+ # transient state (e.g. cached tokens)
95
+ #
96
+ # @param options [Hash] Overrides for the credentials parameters.
97
+ # The following keys are recognized in addition to keys in the
98
+ # Signet::OAuth2::Client
99
+ # * `project_id` the project id to use during the authentication
100
+ # * `quota_project_id` the quota project id to use
101
+ # during the authentication
102
+ def duplicate options = {}
103
+ options = deep_hash_normalize options
104
+ super(
105
+ {
106
+ project_id: @project_id,
107
+ quota_project_id: @quota_project_id
108
+ }.merge(options)
109
+ )
110
+ end
111
+
86
112
  # Revokes the credential
113
+ #
114
+ # @param [Hash] options Options for revoking the credential
115
+ # @option options [Faraday::Connection] :connection The connection to use
116
+ # @raise [Google::Auth::AuthorizationError] If the revocation request fails
87
117
  def revoke! options = {}
88
118
  c = options[:connection] || Faraday.default_connection
89
119
 
@@ -95,8 +125,11 @@ module Google
95
125
  self.refresh_token = nil
96
126
  self.expires_at = 0
97
127
  else
98
- raise(Signet::AuthorizationError,
99
- "Unexpected error code #{resp.status}")
128
+ raise AuthorizationError.with_details(
129
+ "Unexpected error code #{resp.status}",
130
+ credential_type_name: self.class.name,
131
+ principal: principal
132
+ )
100
133
  end
101
134
  end
102
135
  end
@@ -112,6 +145,36 @@ module Google
112
145
  Google::Auth::ScopeUtil.normalize(scope)
113
146
  missing_scope.empty?
114
147
  end
148
+
149
+ # Destructively updates these credentials
150
+ #
151
+ # This method is called by `Signet::OAuth2::Client`'s constructor
152
+ #
153
+ # @param options [Hash] Overrides for the credentials parameters.
154
+ # The following keys are recognized in addition to keys in the
155
+ # Signet::OAuth2::Client
156
+ # * `project_id` the project id to use during the authentication
157
+ # * `quota_project_id` the quota project id to use
158
+ # during the authentication
159
+ # @return [Google::Auth::UserRefreshCredentials]
160
+ def update! options = {}
161
+ # Normalize all keys to symbols to allow indifferent access.
162
+ options = deep_hash_normalize options
163
+
164
+ @project_id = options[:project_id] if options.key? :project_id
165
+ @quota_project_id = options[:quota_project_id] if options.key? :quota_project_id
166
+
167
+ super(options)
168
+
169
+ self
170
+ end
171
+
172
+ # Returns the client ID as the principal for user refresh credentials
173
+ # @private
174
+ # @return [String, Symbol] the client ID or :user_refresh if not available
175
+ def principal
176
+ @client_id || :user_refresh
177
+ end
115
178
  end
116
179
  end
117
180
  end
@@ -16,6 +16,6 @@ module Google
16
16
  # Module Auth provides classes that provide Google-specific authorization
17
17
  # used to access Google APIs.
18
18
  module Auth
19
- VERSION = "1.8.0".freeze
19
+ VERSION = "1.15.0".freeze
20
20
  end
21
21
  end