googleauth 0.11.0 → 0.12.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: '084256f40daf70be42b7489597bbdde5fa0809a0b33690f60307fafa4928d83f'
4
- data.tar.gz: ecf351e58a7ff196ed6e75e9a9bdca3eef918828feb9e65dd5e51869c6c6a235
3
+ metadata.gz: 9f7661bafaadfe1aa8d9a574ace1bae7cb6d708dea4420e54097464d2b449eb0
4
+ data.tar.gz: 6ddd0f1591487041e8cce3d563381877ba27793454e0b8ce1f773186d2fd497e
5
5
  SHA512:
6
- metadata.gz: eb987a66865f02231cd66e8b96bbc8a26c97bb516a0a4324c19b86e9c5a77b84f1af208aa96b2a9f9b3656b0d9b742b0f27b6dcc26060cb9acfd5b03d96beccb
7
- data.tar.gz: b59184d1118bc14fdf5499eb2a19e5ecd9ba4e90c9d1f6ba648e18a648f4162c2a6ef0fe2d65683ecf1b0dfd42500a9fdfbba0d61208538d4c3c4bd85b482f9e
6
+ metadata.gz: 57006f2ea4dd1c5a097b51ae66fc451b2c8598ea8915a6ec74446f7c2d87b0033b0c28fd23b8eae978ce75e17d73ac54c86d7aa03601f3aa61dcee62e85c7cbd
7
+ data.tar.gz: 81306dc3b804115ef426925535b2153916bf37752f81a55e47a3631a77a5e976d40432351eabd452058ad918ff141da2db406128666f993dc64e40d605006a22
@@ -1,3 +1,8 @@
1
+ ### 0.12.0 / 2020-04-08
2
+
3
+ * Support for ID token credentials.
4
+ * Support reading quota_id_project from service account credentials.
5
+
1
6
  ### 0.11.0 / 2020-02-24
2
7
 
3
8
  * Support Faraday 1.x.
data/Gemfile CHANGED
@@ -24,4 +24,5 @@ platforms :jruby do
24
24
  end
25
25
  end
26
26
 
27
+ gem "faraday", "~> 0.17"
27
28
  gem "gems", "~> 1.2"
@@ -9,7 +9,7 @@ Gem::Specification.new do |gem|
9
9
  gem.version = Google::Auth::VERSION
10
10
  gem.authors = ["Tim Emiola"]
11
11
  gem.email = "temiola@google.com"
12
- gem.homepage = "https://github.com/google/google-auth-library-ruby"
12
+ gem.homepage = "https://github.com/googleapis/google-auth-library-ruby"
13
13
  gem.summary = "Google Auth Library for Ruby"
14
14
  gem.license = "Apache-2.0"
15
15
  gem.description = <<-DESCRIPTION
@@ -32,6 +32,6 @@ Gem::Specification.new do |gem|
32
32
  gem.add_dependency "memoist", "~> 0.16"
33
33
  gem.add_dependency "multi_json", "~> 1.11"
34
34
  gem.add_dependency "os", ">= 0.9", "< 2.0"
35
- gem.add_dependency "signet", "~> 0.12"
35
+ gem.add_dependency "signet", "~> 0.14"
36
36
  gem.add_development_dependency "yard", "~> 0.9"
37
37
  end
@@ -51,8 +51,10 @@ module Google
51
51
  class GCECredentials < Signet::OAuth2::Client
52
52
  # The IP Address is used in the URIs to speed up failures on non-GCE
53
53
  # systems.
54
- COMPUTE_AUTH_TOKEN_URI = "http://169.254.169.254/computeMetadata/v1/"\
55
- "instance/service-accounts/default/token".freeze
54
+ COMPUTE_AUTH_TOKEN_URI =
55
+ "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
56
+ COMPUTE_ID_TOKEN_URI =
57
+ "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity".freeze
56
58
  COMPUTE_CHECK_URI = "http://169.254.169.254".freeze
57
59
 
58
60
  class << self
@@ -82,12 +84,18 @@ module Google
82
84
  def fetch_access_token options = {}
83
85
  c = options[:connection] || Faraday.default_connection
84
86
  retry_with_error do
87
+ uri = target_audience ? COMPUTE_ID_TOKEN_URI : COMPUTE_AUTH_TOKEN_URI
88
+ query = target_audience ? { "audience" => target_audience, "format" => "full" } : nil
85
89
  headers = { "Metadata-Flavor" => "Google" }
86
- resp = c.get COMPUTE_AUTH_TOKEN_URI, nil, headers
90
+ resp = c.get uri, query, headers
87
91
  case resp.status
88
92
  when 200
89
- Signet::OAuth2.parse_credentials(resp.body,
90
- resp.headers["content-type"])
93
+ content_type = resp.headers["content-type"]
94
+ if content_type == "text/html"
95
+ { (target_audience ? "id_token" : "access_token") => resp.body }
96
+ else
97
+ Signet::OAuth2.parse_credentials resp.body, content_type
98
+ end
91
99
  when 404
92
100
  raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
93
101
  else
@@ -47,6 +47,8 @@ module Google
47
47
  # The default target audience ID to be used when none is provided during initialization.
48
48
  AUDIENCE = "https://oauth2.googleapis.com/token".freeze
49
49
 
50
+ @audience = @scope = @target_audience = @env_vars = @paths = nil
51
+
50
52
  ##
51
53
  # The default token credential URI to be used when none is provided during initialization.
52
54
  # The URI is the authorization server's HTTP endpoint capable of issuing tokens and
@@ -97,20 +99,25 @@ module Google
97
99
  # A scope is an access range defined by the authorization server.
98
100
  # The scope can be a single value or a list of values.
99
101
  #
102
+ # Either {#scope} or {#target_audience}, but not both, should be non-nil.
103
+ # If {#scope} is set, this credential will produce access tokens.
104
+ # If {#target_audience} is set, this credential will produce ID tokens.
105
+ #
100
106
  # @return [String, Array<String>]
101
107
  #
102
108
  def self.scope
103
109
  return @scope unless @scope.nil?
104
110
 
105
- tmp_scope = []
106
- # Pull in values is the SCOPE constant exists.
107
- tmp_scope << const_get(:SCOPE) if const_defined? :SCOPE
108
- tmp_scope.flatten.uniq
111
+ Array(const_get(:SCOPE)).flatten.uniq if const_defined? :SCOPE
109
112
  end
110
113
 
111
114
  ##
112
115
  # Sets the default scope to be used when none is provided during initialization.
113
116
  #
117
+ # Either {#scope} or {#target_audience}, but not both, should be non-nil.
118
+ # If {#scope} is set, this credential will produce access tokens.
119
+ # If {#target_audience} is set, this credential will produce ID tokens.
120
+ #
114
121
  # @param [String, Array<String>] new_scope
115
122
  # @return [String, Array<String>]
116
123
  #
@@ -119,6 +126,34 @@ module Google
119
126
  @scope = new_scope
120
127
  end
121
128
 
129
+ ##
130
+ # The default final target audience for ID tokens, to be used when none
131
+ # is provided during initialization.
132
+ #
133
+ # Either {#scope} or {#target_audience}, but not both, should be non-nil.
134
+ # If {#scope} is set, this credential will produce access tokens.
135
+ # If {#target_audience} is set, this credential will produce ID tokens.
136
+ #
137
+ # @return [String]
138
+ #
139
+ def self.target_audience
140
+ @target_audience
141
+ end
142
+
143
+ ##
144
+ # Sets the default final target audience for ID tokens, to be used when none
145
+ # is provided during initialization.
146
+ #
147
+ # Either {#scope} or {#target_audience}, but not both, should be non-nil.
148
+ # If {#scope} is set, this credential will produce access tokens.
149
+ # If {#target_audience} is set, this credential will produce ID tokens.
150
+ #
151
+ # @param [String] new_target_audience
152
+ #
153
+ def self.target_audience= new_target_audience
154
+ @target_audience = new_target_audience
155
+ end
156
+
122
157
  ##
123
158
  # The environment variables to search for credentials. Values can either be a file path to the
124
159
  # credentials file, or the JSON contents of the credentials file.
@@ -185,6 +220,13 @@ module Google
185
220
  #
186
221
  attr_reader :project_id
187
222
 
223
+ ##
224
+ # Identifier for a separate project used for billing/quota, if any.
225
+ #
226
+ # @return [String,nil]
227
+ #
228
+ attr_reader :quota_project_id
229
+
188
230
  # @private Delegate client methods to the client object.
189
231
  extend Forwardable
190
232
 
@@ -201,6 +243,9 @@ module Google
201
243
  # @return [String, Array<String>] The scope for this client. A scope is an access range
202
244
  # defined by the authorization server. The scope can be a single value or a list of values.
203
245
  #
246
+ # @!attribute [r] target_audience
247
+ # @return [String] The final target audience for ID tokens returned by this credential.
248
+ #
204
249
  # @!attribute [r] issuer
205
250
  # @return [String] The issuer ID associated with this client.
206
251
  #
@@ -213,9 +258,7 @@ module Google
213
258
  #
214
259
  def_delegators :@client,
215
260
  :token_credential_uri, :audience,
216
- :scope, :issuer, :signing_key, :updater_proc
217
-
218
- # rubocop:disable Metrics/AbcSize
261
+ :scope, :issuer, :signing_key, :updater_proc, :target_audience
219
262
 
220
263
  ##
221
264
  # Creates a new Credentials instance with the provided auth credentials, and with the default
@@ -236,23 +279,15 @@ module Google
236
279
  # * +:default_connection+ - the default connection to use for the client
237
280
  #
238
281
  def initialize keyfile, options = {}
239
- scope = options[:scope]
240
282
  verify_keyfile_provided! keyfile
241
283
  @project_id = options["project_id"] || options["project"]
284
+ @quota_project_id = options["quota_project_id"]
242
285
  if keyfile.is_a? Signet::OAuth2::Client
243
- @client = keyfile
244
- @project_id ||= keyfile.project_id if keyfile.respond_to? :project_id
286
+ update_from_signet keyfile
245
287
  elsif keyfile.is_a? Hash
246
- hash = stringify_hash_keys keyfile
247
- hash["scope"] ||= scope
248
- @client = init_client hash, options
249
- @project_id ||= (hash["project_id"] || hash["project"])
288
+ update_from_hash keyfile, options
250
289
  else
251
- verify_keyfile_exists! keyfile
252
- json = JSON.parse ::File.read(keyfile)
253
- json["scope"] ||= scope
254
- @project_id ||= (json["project_id"] || json["project"])
255
- @client = init_client json, options
290
+ update_from_filepath keyfile, options
256
291
  end
257
292
  CredentialsLoader.warn_if_cloud_sdk_credentials @client.client_id
258
293
  @project_id ||= CredentialsLoader.load_gcloud_project_id
@@ -261,7 +296,6 @@ module Google
261
296
  @paths = nil
262
297
  @scope = nil
263
298
  end
264
- # rubocop:enable Metrics/AbcSize
265
299
 
266
300
  ##
267
301
  # Creates a new Credentials instance with auth credentials acquired by searching the
@@ -323,7 +357,8 @@ module Google
323
357
  # @private Lookup Credentials using Google::Auth.get_application_default.
324
358
  def self.from_application_default options
325
359
  scope = options[:scope] || self.scope
326
- client = Google::Auth.get_application_default scope
360
+ auth_opts = { target_audience: options[:target_audience] || target_audience }
361
+ client = Google::Auth.get_application_default scope, auth_opts
327
362
  new client, options
328
363
  end
329
364
 
@@ -362,14 +397,46 @@ module Google
362
397
  options["token_credential_uri"] ||= self.class.token_credential_uri
363
398
  options["audience"] ||= self.class.audience
364
399
  options["scope"] ||= self.class.scope
400
+ options["target_audience"] ||= self.class.target_audience
365
401
 
402
+ if !Array(options["scope"]).empty? && options["target_audience"]
403
+ raise ArgumentError, "Cannot specify both scope and target_audience"
404
+ end
405
+
406
+ needs_scope = options["target_audience"].nil?
366
407
  # client options for initializing signet client
367
408
  { token_credential_uri: options["token_credential_uri"],
368
409
  audience: options["audience"],
369
- scope: Array(options["scope"]),
410
+ scope: (needs_scope ? Array(options["scope"]) : nil),
411
+ target_audience: options["target_audience"],
370
412
  issuer: options["client_email"],
371
413
  signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) }
372
414
  end
415
+
416
+ def update_from_signet client
417
+ @project_id ||= client.project_id if client.respond_to? :project_id
418
+ @quota_project_id ||= client.quota_project_id if client.respond_to? :quota_project_id
419
+ @client = client
420
+ end
421
+
422
+ def update_from_hash hash, options
423
+ hash = stringify_hash_keys hash
424
+ hash["scope"] ||= options[:scope]
425
+ hash["target_audience"] ||= options[:target_audience]
426
+ @project_id ||= (hash["project_id"] || hash["project"])
427
+ @quota_project_id ||= hash["quota_project_id"]
428
+ @client = init_client hash, options
429
+ end
430
+
431
+ def update_from_filepath path, options
432
+ verify_keyfile_exists! path
433
+ json = JSON.parse ::File.read(path)
434
+ json["scope"] ||= options[:scope]
435
+ json["target_audience"] ||= options[:target_audience]
436
+ @project_id ||= (json["project_id"] || json["project"])
437
+ @quota_project_id ||= json["quota_project_id"]
438
+ @client = init_client json, options
439
+ end
373
440
  end
374
441
  end
375
442
  end
@@ -38,8 +38,12 @@ module Google
38
38
  json_key = MultiJson.load json_key_io.read
39
39
  raise "missing client_email" unless json_key.key? "client_email"
40
40
  raise "missing private_key" unless json_key.key? "private_key"
41
- project_id = json_key["project_id"]
42
- [json_key["private_key"], json_key["client_email"], project_id]
41
+ [
42
+ json_key["private_key"],
43
+ json_key["client_email"],
44
+ json_key["project_id"],
45
+ json_key["quota_project_id"]
46
+ ]
43
47
  end
44
48
  end
45
49
  end
@@ -51,28 +51,34 @@ module Google
51
51
  extend CredentialsLoader
52
52
  extend JsonKeyReader
53
53
  attr_reader :project_id
54
+ attr_reader :quota_project_id
54
55
 
55
56
  # Creates a ServiceAccountCredentials.
56
57
  #
57
58
  # @param json_key_io [IO] an IO from which the JSON key can be read
58
59
  # @param scope [string|array|nil] the scope(s) to access
59
60
  def self.make_creds options = {}
60
- json_key_io, scope = options.values_at :json_key_io, :scope
61
+ json_key_io, scope, target_audience = options.values_at :json_key_io, :scope, :target_audience
62
+ raise ArgumentError, "Cannot specify both scope and target_audience" if scope && target_audience
63
+
61
64
  if json_key_io
62
- private_key, client_email, project_id = read_json_key json_key_io
65
+ private_key, client_email, project_id, quota_project_id = read_json_key json_key_io
63
66
  else
64
67
  private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR]
65
68
  client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
66
69
  project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
70
+ quota_project_id = nil
67
71
  end
68
72
  project_id ||= CredentialsLoader.load_gcloud_project_id
69
73
 
70
74
  new(token_credential_uri: TOKEN_CRED_URI,
71
75
  audience: TOKEN_CRED_URI,
72
76
  scope: scope,
77
+ target_audience: target_audience,
73
78
  issuer: client_email,
74
79
  signing_key: OpenSSL::PKey::RSA.new(private_key),
75
- project_id: project_id)
80
+ project_id: project_id,
81
+ quota_project_id: quota_project_id)
76
82
  .configure_connection(options)
77
83
  end
78
84
 
@@ -87,6 +93,7 @@ module Google
87
93
 
88
94
  def initialize options = {}
89
95
  @project_id = options[:project_id]
96
+ @quota_project_id = options[:quota_project_id]
90
97
  super options
91
98
  end
92
99
 
@@ -133,6 +140,7 @@ module Google
133
140
  extend CredentialsLoader
134
141
  extend JsonKeyReader
135
142
  attr_reader :project_id
143
+ attr_reader :quota_project_id
136
144
 
137
145
  # make_creds proxies the construction of a credentials instance
138
146
  #
@@ -151,12 +159,13 @@ module Google
151
159
  def initialize options = {}
152
160
  json_key_io = options[:json_key_io]
153
161
  if json_key_io
154
- @private_key, @issuer, @project_id =
162
+ @private_key, @issuer, @project_id, @quota_project_id =
155
163
  self.class.read_json_key json_key_io
156
164
  else
157
165
  @private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR]
158
166
  @issuer = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
159
167
  @project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
168
+ @quota_project_id = nil
160
169
  end
161
170
  @project_id ||= CredentialsLoader.load_gcloud_project_id
162
171
  @signing_key = OpenSSL::PKey::RSA.new @private_key
@@ -48,8 +48,9 @@ module Signet
48
48
  def apply! a_hash, opts = {}
49
49
  # fetch the access token there is currently not one, or if the client
50
50
  # has expired
51
- fetch_access_token! opts if access_token.nil? || expires_within?(60)
52
- a_hash[AUTH_METADATA_KEY] = "Bearer #{access_token}"
51
+ token_type = target_audience ? :id_token : :access_token
52
+ fetch_access_token! opts if send(token_type).nil? || expires_within?(60)
53
+ a_hash[AUTH_METADATA_KEY] = "Bearer #{send token_type}"
53
54
  end
54
55
 
55
56
  # Returns a clone of a_hash updated with the authentication token
@@ -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.11.0".freeze
34
+ VERSION = "0.12.0".freeze
35
35
  end
36
36
  end
@@ -45,26 +45,37 @@ shared_examples "apply/apply! are OK" do
45
45
  # auth client
46
46
  describe "#fetch_access_token" do
47
47
  let(:token) { "1/abcdef1234567890" }
48
- let :stub do
48
+ let :access_stub do
49
49
  make_auth_stubs access_token: token
50
50
  end
51
+ let :id_stub do
52
+ make_auth_stubs id_token: token
53
+ end
51
54
 
52
55
  it "should set access_token to the fetched value" do
53
- stub
56
+ access_stub
54
57
  @client.fetch_access_token!
55
58
  expect(@client.access_token).to eq(token)
56
- expect(stub).to have_been_requested
59
+ expect(access_stub).to have_been_requested
60
+ end
61
+
62
+ it "should set id_token to the fetched value" do
63
+ skip unless @id_client
64
+ id_stub
65
+ @id_client.fetch_access_token!
66
+ expect(@id_client.id_token).to eq(token)
67
+ expect(id_stub).to have_been_requested
57
68
  end
58
69
 
59
70
  it "should notify refresh listeners after updating" do
60
- stub
71
+ access_stub
61
72
  expect do |b|
62
73
  @client.on_refresh(&b)
63
74
  @client.fetch_access_token!
64
75
  end.to yield_with_args(have_attributes(
65
76
  access_token: "1/abcdef1234567890"
66
77
  ))
67
- expect(stub).to have_been_requested
78
+ expect(access_stub).to have_been_requested
68
79
  end
69
80
  end
70
81
 
@@ -37,23 +37,32 @@ require "googleauth/compute_engine"
37
37
  require "spec_helper"
38
38
 
39
39
  describe Google::Auth::GCECredentials do
40
- MD_URI = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
40
+ MD_ACCESS_URI = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
41
+ MD_ID_URI = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://pubsub.googleapis.com/&format=full".freeze
41
42
  GCECredentials = Google::Auth::GCECredentials
42
43
 
43
44
  before :example do
44
45
  @client = GCECredentials.new
46
+ @id_client = GCECredentials.new target_audience: "https://pubsub.googleapis.com/"
45
47
  end
46
48
 
47
- def make_auth_stubs opts = {}
48
- access_token = opts[:access_token] || ""
49
- body = MultiJson.dump("access_token" => access_token,
50
- "token_type" => "Bearer",
51
- "expires_in" => 3600)
52
- stub_request(:get, MD_URI)
53
- .with(headers: { "Metadata-Flavor" => "Google" })
54
- .to_return(body: body,
55
- status: 200,
56
- headers: { "Content-Type" => "application/json" })
49
+ def make_auth_stubs opts
50
+ if opts[:access_token]
51
+ body = MultiJson.dump("access_token" => opts[:access_token],
52
+ "token_type" => "Bearer",
53
+ "expires_in" => 3600)
54
+ stub_request(:get, MD_ACCESS_URI)
55
+ .with(headers: { "Metadata-Flavor" => "Google" })
56
+ .to_return(body: body,
57
+ status: 200,
58
+ headers: { "Content-Type" => "application/json" })
59
+ elsif opts[:id_token]
60
+ stub_request(:get, MD_ID_URI)
61
+ .with(headers: { "Metadata-Flavor" => "Google" })
62
+ .to_return(body: opts[:id_token],
63
+ status: 200,
64
+ headers: { "Content-Type" => "text/html" })
65
+ end
57
66
  end
58
67
 
59
68
  it_behaves_like "apply/apply! are OK"
@@ -61,7 +70,7 @@ describe Google::Auth::GCECredentials do
61
70
  context "metadata is unavailable" do
62
71
  describe "#fetch_access_token" do
63
72
  it "should fail if the metadata request returns a 404" do
64
- stub = stub_request(:get, MD_URI)
73
+ stub = stub_request(:get, MD_ACCESS_URI)
65
74
  .to_return(status: 404,
66
75
  headers: { "Metadata-Flavor" => "Google" })
67
76
  expect { @client.fetch_access_token! }
@@ -70,7 +79,7 @@ describe Google::Auth::GCECredentials do
70
79
  end
71
80
 
72
81
  it "should fail if the metadata request returns an unexpected code" do
73
- stub = stub_request(:get, MD_URI)
82
+ stub = stub_request(:get, MD_ACCESS_URI)
74
83
  .to_return(status: 503,
75
84
  headers: { "Metadata-Flavor" => "Google" })
76
85
  expect { @client.fetch_access_token! }
@@ -36,12 +36,13 @@ require "googleauth"
36
36
  describe Google::Auth::Credentials, :private do
37
37
  let :default_keyfile_hash do
38
38
  {
39
- "private_key_id" => "testabc1234567890xyz",
40
- "private_key" => "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBAOyi0Hy1l4Ym2m2o71Q0TF4O9E81isZEsX0bb+Bqz1SXEaSxLiXM\nUZE8wu0eEXivXuZg6QVCW/5l+f2+9UPrdNUCAwEAAQJAJkqubA/Chj3RSL92guy3\nktzeodarLyw8gF8pOmpuRGSiEo/OLTeRUMKKD1/kX4f9sxf3qDhB4e7dulXR1co/\nIQIhAPx8kMW4XTTL6lJYd2K5GrH8uBMp8qL5ya3/XHrBgw3dAiEA7+3Iw3ULTn2I\n1J34WlJ2D5fbzMzB4FAHUNEV7Ys3f1kCIQDtUahCMChrl7+H5t9QS+xrn77lRGhs\nB50pjvy95WXpgQIhAI2joW6JzTfz8fAapb+kiJ/h9Vcs1ZN3iyoRlNFb61JZAiA8\nNy5NyNrMVwtB/lfJf1dAK/p/Bwd8LZLtgM6PapRfgw==\n-----END RSA PRIVATE KEY-----\n",
41
- "client_email" => "credz-testabc1234567890xyz@developer.gserviceaccount.com",
42
- "client_id" => "credz-testabc1234567890xyz.apps.googleusercontent.com",
43
- "type" => "service_account",
44
- "project_id" => "a_project_id"
39
+ "private_key_id" => "testabc1234567890xyz",
40
+ "private_key" => "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBAOyi0Hy1l4Ym2m2o71Q0TF4O9E81isZEsX0bb+Bqz1SXEaSxLiXM\nUZE8wu0eEXivXuZg6QVCW/5l+f2+9UPrdNUCAwEAAQJAJkqubA/Chj3RSL92guy3\nktzeodarLyw8gF8pOmpuRGSiEo/OLTeRUMKKD1/kX4f9sxf3qDhB4e7dulXR1co/\nIQIhAPx8kMW4XTTL6lJYd2K5GrH8uBMp8qL5ya3/XHrBgw3dAiEA7+3Iw3ULTn2I\n1J34WlJ2D5fbzMzB4FAHUNEV7Ys3f1kCIQDtUahCMChrl7+H5t9QS+xrn77lRGhs\nB50pjvy95WXpgQIhAI2joW6JzTfz8fAapb+kiJ/h9Vcs1ZN3iyoRlNFb61JZAiA8\nNy5NyNrMVwtB/lfJf1dAK/p/Bwd8LZLtgM6PapRfgw==\n-----END RSA PRIVATE KEY-----\n",
41
+ "client_email" => "credz-testabc1234567890xyz@developer.gserviceaccount.com",
42
+ "client_id" => "credz-testabc1234567890xyz.apps.googleusercontent.com",
43
+ "type" => "service_account",
44
+ "project_id" => "a_project_id",
45
+ "quota_project_id" => "b_project_id"
45
46
  }
46
47
  end
47
48
 
@@ -118,6 +119,7 @@ describe Google::Auth::Credentials, :private do
118
119
  expect(creds).to be_a_kind_of(TestCredentials1)
119
120
  expect(creds.client).to eq(mocked_signet)
120
121
  expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
122
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
121
123
  end
122
124
 
123
125
  it "subclasses can use PATH_ENV_VARS to get keyfile path" do
@@ -153,6 +155,7 @@ describe Google::Auth::Credentials, :private do
153
155
  expect(creds).to be_a_kind_of(TestCredentials2)
154
156
  expect(creds.client).to eq(mocked_signet)
155
157
  expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
158
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
156
159
  end
157
160
 
158
161
  it "subclasses can use JSON_ENV_VARS to get keyfile contents" do
@@ -190,6 +193,7 @@ describe Google::Auth::Credentials, :private do
190
193
  expect(creds).to be_a_kind_of(TestCredentials3)
191
194
  expect(creds.client).to eq(mocked_signet)
192
195
  expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
196
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
193
197
  end
194
198
 
195
199
  it "subclasses can use DEFAULT_PATHS to get keyfile path" do
@@ -225,6 +229,7 @@ describe Google::Auth::Credentials, :private do
225
229
  expect(creds).to be_a_kind_of(TestCredentials4)
226
230
  expect(creds.client).to eq(mocked_signet)
227
231
  expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
232
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
228
233
  end
229
234
 
230
235
  it "subclasses that find no matches default to Google::Auth.get_application_default" do
@@ -266,6 +271,7 @@ describe Google::Auth::Credentials, :private do
266
271
  expect(creds).to be_a_kind_of(TestCredentials5)
267
272
  expect(creds.client).to eq(mocked_signet)
268
273
  expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
274
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
269
275
  end
270
276
  end
271
277
 
@@ -305,6 +311,7 @@ describe Google::Auth::Credentials, :private do
305
311
  expect(creds).to be_a_kind_of(TestCredentials11)
306
312
  expect(creds.client).to eq(mocked_signet)
307
313
  expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
314
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
308
315
  end
309
316
 
310
317
  it "subclasses can use PATH_ENV_VARS to get keyfile path" do
@@ -339,6 +346,7 @@ describe Google::Auth::Credentials, :private do
339
346
  expect(creds).to be_a_kind_of(TestCredentials12)
340
347
  expect(creds.client).to eq(mocked_signet)
341
348
  expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
349
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
342
350
  end
343
351
 
344
352
  it "subclasses can use JSON_ENV_VARS to get keyfile contents" do
@@ -375,6 +383,7 @@ describe Google::Auth::Credentials, :private do
375
383
  expect(creds).to be_a_kind_of(TestCredentials13)
376
384
  expect(creds.client).to eq(mocked_signet)
377
385
  expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
386
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
378
387
  end
379
388
 
380
389
  it "subclasses can use DEFAULT_PATHS to get keyfile path" do
@@ -409,6 +418,7 @@ describe Google::Auth::Credentials, :private do
409
418
  expect(creds).to be_a_kind_of(TestCredentials14)
410
419
  expect(creds.client).to eq(mocked_signet)
411
420
  expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
421
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
412
422
  end
413
423
 
414
424
  it "subclasses that find no matches default to Google::Auth.get_application_default" do
@@ -449,6 +459,7 @@ describe Google::Auth::Credentials, :private do
449
459
  expect(creds).to be_a_kind_of(TestCredentials15)
450
460
  expect(creds.client).to eq(mocked_signet)
451
461
  expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
462
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
452
463
  end
453
464
  end
454
465
 
@@ -112,12 +112,13 @@ describe Google::Auth::ServiceAccountCredentials do
112
112
  let(:client_email) { "app@developer.gserviceaccount.com" }
113
113
  let :cred_json do
114
114
  {
115
- private_key_id: "a_private_key_id",
116
- private_key: @key.to_pem,
117
- client_email: client_email,
118
- client_id: "app.apps.googleusercontent.com",
119
- type: "service_account",
120
- project_id: "a_project_id"
115
+ private_key_id: "a_private_key_id",
116
+ private_key: @key.to_pem,
117
+ client_email: client_email,
118
+ client_id: "app.apps.googleusercontent.com",
119
+ type: "service_account",
120
+ project_id: "a_project_id",
121
+ quota_project_id: "b_project_id"
121
122
  }
122
123
  end
123
124
 
@@ -127,24 +128,28 @@ describe Google::Auth::ServiceAccountCredentials do
127
128
  json_key_io: StringIO.new(cred_json_text),
128
129
  scope: "https://www.googleapis.com/auth/userinfo.profile"
129
130
  )
131
+ @id_client = ServiceAccountCredentials.make_creds(
132
+ json_key_io: StringIO.new(cred_json_text),
133
+ target_audience: "https://pubsub.googleapis.com/"
134
+ )
130
135
  end
131
136
 
132
- def make_auth_stubs opts = {}
133
- access_token = opts[:access_token] || ""
134
- body = MultiJson.dump("access_token" => access_token,
135
- "token_type" => "Bearer",
136
- "expires_in" => 3600)
137
+ def make_auth_stubs opts
138
+ body_fields = { "token_type" => "Bearer", "expires_in" => 3600 }
139
+ body_fields["access_token"] = opts[:access_token] if opts[:access_token]
140
+ body_fields["id_token"] = opts[:id_token] if opts[:id_token]
141
+ body = MultiJson.dump body_fields
137
142
  blk = proc do |request|
138
143
  params = Addressable::URI.form_unencode request.body
139
- _claim, _header = JWT.decode(params.assoc("assertion").last,
140
- @key.public_key, true,
141
- algorithm: "RS256")
144
+ claim, _header = JWT.decode(params.assoc("assertion").last,
145
+ @key.public_key, true,
146
+ algorithm: "RS256")
147
+ !opts[:id_token] || claim["target_audience"] == "https://pubsub.googleapis.com/"
142
148
  end
143
149
  stub_request(:post, "https://www.googleapis.com/oauth2/v4/token")
144
150
  .with(body: hash_including(
145
151
  "grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer"
146
- ),
147
- &blk)
152
+ ), &blk)
148
153
  .to_return(body: body,
149
154
  status: 200,
150
155
  headers: { "Content-Type" => "application/json" })
@@ -285,6 +290,7 @@ describe Google::Auth::ServiceAccountCredentials do
285
290
  ENV["APPDATA"] = dir
286
291
  credentials = @clz.from_well_known_path @scope
287
292
  expect(credentials.project_id).to eq(cred_json[:project_id])
293
+ expect(credentials.quota_project_id).to eq(cred_json[:quota_project_id])
288
294
  end
289
295
  end
290
296
 
@@ -476,6 +482,7 @@ describe Google::Auth::ServiceAccountJwtHeaderCredentials do
476
482
  ENV["APPDATA"] = dir
477
483
  credentials = clz.from_well_known_path @scope
478
484
  expect(credentials.project_id).to eq(cred_json[:project_id])
485
+ expect(credentials.quota_project_id).to be_nil
479
486
  end
480
487
  end
481
488
  end
@@ -47,18 +47,26 @@ describe Signet::OAuth2::Client do
47
47
  audience: "https://oauth2.googleapis.com/token",
48
48
  signing_key: @key
49
49
  )
50
+ @id_client = Signet::OAuth2::Client.new(
51
+ token_credential_uri: "https://oauth2.googleapis.com/token",
52
+ target_audience: "https://pubsub.googleapis.com/",
53
+ issuer: "app@example.com",
54
+ audience: "https://oauth2.googleapis.com/token",
55
+ signing_key: @key
56
+ )
50
57
  end
51
58
 
52
59
  def make_auth_stubs opts
53
- access_token = opts[:access_token] || ""
54
- body = MultiJson.dump("access_token" => access_token,
55
- "token_type" => "Bearer",
56
- "expires_in" => 3600)
60
+ body_fields = { "token_type" => "Bearer", "expires_in" => 3600 }
61
+ body_fields["access_token"] = opts[:access_token] if opts[:access_token]
62
+ body_fields["id_token"] = opts[:id_token] if opts[:id_token]
63
+ body = MultiJson.dump body_fields
57
64
  blk = proc do |request|
58
65
  params = Addressable::URI.form_unencode request.body
59
- _claim, _header = JWT.decode(params.assoc("assertion").last,
60
- @key.public_key, true,
61
- algorithm: "RS256")
66
+ claim, _header = JWT.decode(params.assoc("assertion").last,
67
+ @key.public_key, true,
68
+ algorithm: "RS256")
69
+ !opts[:id_token] || claim["target_audience"] == "https://pubsub.googleapis.com/"
62
70
  end
63
71
  with_params = { body: hash_including(
64
72
  "grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer"
@@ -64,7 +64,7 @@ describe Google::Auth::UserRefreshCredentials do
64
64
  )
65
65
  end
66
66
 
67
- def make_auth_stubs opts = {}
67
+ def make_auth_stubs opts
68
68
  access_token = opts[:access_token] || ""
69
69
  body = MultiJson.dump("access_token" => access_token,
70
70
  "token_type" => "Bearer",
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.11.0
4
+ version: 0.12.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-02-24 00:00:00.000000000 Z
11
+ date: 2020-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -104,14 +104,14 @@ dependencies:
104
104
  requirements:
105
105
  - - "~>"
106
106
  - !ruby/object:Gem::Version
107
- version: '0.12'
107
+ version: '0.14'
108
108
  type: :runtime
109
109
  prerelease: false
110
110
  version_requirements: !ruby/object:Gem::Requirement
111
111
  requirements:
112
112
  - - "~>"
113
113
  - !ruby/object:Gem::Version
114
- version: '0.12'
114
+ version: '0.14'
115
115
  - !ruby/object:Gem::Dependency
116
116
  name: yard
117
117
  requirement: !ruby/object:Gem::Requirement
@@ -203,7 +203,7 @@ files:
203
203
  - spec/googleauth/user_refresh_spec.rb
204
204
  - spec/googleauth/web_user_authorizer_spec.rb
205
205
  - spec/spec_helper.rb
206
- homepage: https://github.com/google/google-auth-library-ruby
206
+ homepage: https://github.com/googleapis/google-auth-library-ruby
207
207
  licenses:
208
208
  - Apache-2.0
209
209
  metadata: {}