googleauth 0.1.0 → 0.16.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +7 -0
  3. data/.github/CONTRIBUTING.md +74 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +36 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
  6. data/.github/ISSUE_TEMPLATE/support_request.md +7 -0
  7. data/.github/workflows/ci.yml +55 -0
  8. data/.github/workflows/release-please.yml +39 -0
  9. data/.gitignore +3 -0
  10. data/.kokoro/populate-secrets.sh +76 -0
  11. data/.kokoro/release.cfg +52 -0
  12. data/.kokoro/release.sh +18 -0
  13. data/.kokoro/trampoline_v2.sh +489 -0
  14. data/.repo-metadata.json +5 -0
  15. data/.rubocop.yml +17 -0
  16. data/.toys/.toys.rb +45 -0
  17. data/.toys/ci.rb +43 -0
  18. data/.toys/kokoro/.toys.rb +66 -0
  19. data/.toys/kokoro/publish-docs.rb +67 -0
  20. data/.toys/kokoro/publish-gem.rb +53 -0
  21. data/.toys/linkinator.rb +43 -0
  22. data/.trampolinerc +48 -0
  23. data/CHANGELOG.md +192 -0
  24. data/CODE_OF_CONDUCT.md +43 -0
  25. data/Gemfile +22 -1
  26. data/{COPYING → LICENSE} +0 -0
  27. data/README.md +140 -17
  28. data/googleauth.gemspec +28 -28
  29. data/integration/helper.rb +31 -0
  30. data/integration/id_tokens/key_source_test.rb +74 -0
  31. data/lib/googleauth.rb +7 -37
  32. data/lib/googleauth/application_default.rb +81 -0
  33. data/lib/googleauth/client_id.rb +104 -0
  34. data/lib/googleauth/compute_engine.rb +73 -26
  35. data/lib/googleauth/credentials.rb +561 -0
  36. data/lib/googleauth/credentials_loader.rb +207 -0
  37. data/lib/googleauth/default_credentials.rb +93 -0
  38. data/lib/googleauth/iam.rb +75 -0
  39. data/lib/googleauth/id_tokens.rb +233 -0
  40. data/lib/googleauth/id_tokens/errors.rb +71 -0
  41. data/lib/googleauth/id_tokens/key_sources.rb +396 -0
  42. data/lib/googleauth/id_tokens/verifier.rb +142 -0
  43. data/lib/googleauth/json_key_reader.rb +50 -0
  44. data/lib/googleauth/scope_util.rb +61 -0
  45. data/lib/googleauth/service_account.rb +175 -67
  46. data/lib/googleauth/signet.rb +69 -8
  47. data/lib/googleauth/stores/file_token_store.rb +65 -0
  48. data/lib/googleauth/stores/redis_token_store.rb +96 -0
  49. data/lib/googleauth/token_store.rb +69 -0
  50. data/lib/googleauth/user_authorizer.rb +285 -0
  51. data/lib/googleauth/user_refresh.rb +129 -0
  52. data/lib/googleauth/version.rb +1 -1
  53. data/lib/googleauth/web_user_authorizer.rb +295 -0
  54. data/spec/googleauth/apply_auth_examples.rb +96 -94
  55. data/spec/googleauth/client_id_spec.rb +160 -0
  56. data/spec/googleauth/compute_engine_spec.rb +125 -55
  57. data/spec/googleauth/credentials_spec.rb +600 -0
  58. data/spec/googleauth/get_application_default_spec.rb +232 -80
  59. data/spec/googleauth/iam_spec.rb +80 -0
  60. data/spec/googleauth/scope_util_spec.rb +77 -0
  61. data/spec/googleauth/service_account_spec.rb +422 -68
  62. data/spec/googleauth/signet_spec.rb +101 -25
  63. data/spec/googleauth/stores/file_token_store_spec.rb +57 -0
  64. data/spec/googleauth/stores/redis_token_store_spec.rb +50 -0
  65. data/spec/googleauth/stores/store_examples.rb +58 -0
  66. data/spec/googleauth/user_authorizer_spec.rb +343 -0
  67. data/spec/googleauth/user_refresh_spec.rb +359 -0
  68. data/spec/googleauth/web_user_authorizer_spec.rb +172 -0
  69. data/spec/spec_helper.rb +51 -10
  70. data/test/helper.rb +33 -0
  71. data/test/id_tokens/key_sources_test.rb +240 -0
  72. data/test/id_tokens/verifier_test.rb +269 -0
  73. metadata +112 -75
  74. data/.travis.yml +0 -18
  75. data/CONTRIBUTING.md +0 -32
  76. data/Rakefile +0 -15
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2020 Google LLC
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are
7
+ # met:
8
+ #
9
+ # * Redistributions of source code must retain the above copyright
10
+ # notice, this list of conditions and the following disclaimer.
11
+ # * Redistributions in binary form must reproduce the above
12
+ # copyright notice, this list of conditions and the following disclaimer
13
+ # in the documentation and/or other materials provided with the
14
+ # distribution.
15
+ # * Neither the name of Google Inc. nor the names of its
16
+ # contributors may be used to endorse or promote products derived from
17
+ # this software without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+
32
+ module Google
33
+ module Auth
34
+ module IDTokens
35
+ ##
36
+ # Failed to obtain keys from the key source.
37
+ #
38
+ class KeySourceError < StandardError; end
39
+
40
+ ##
41
+ # Failed to verify a token.
42
+ #
43
+ class VerificationError < StandardError; end
44
+
45
+ ##
46
+ # Failed to verify a token because it is expired.
47
+ #
48
+ class ExpiredTokenError < VerificationError; end
49
+
50
+ ##
51
+ # Failed to verify a token because its signature did not match.
52
+ #
53
+ class SignatureError < VerificationError; end
54
+
55
+ ##
56
+ # Failed to verify a token because its issuer did not match.
57
+ #
58
+ class IssuerMismatchError < VerificationError; end
59
+
60
+ ##
61
+ # Failed to verify a token because its audience did not match.
62
+ #
63
+ class AudienceMismatchError < VerificationError; end
64
+
65
+ ##
66
+ # Failed to verify a token because its authorized party did not match.
67
+ #
68
+ class AuthorizedPartyMismatchError < VerificationError; end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,396 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2020 Google LLC
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are
7
+ # met:
8
+ #
9
+ # * Redistributions of source code must retain the above copyright
10
+ # notice, this list of conditions and the following disclaimer.
11
+ # * Redistributions in binary form must reproduce the above
12
+ # copyright notice, this list of conditions and the following disclaimer
13
+ # in the documentation and/or other materials provided with the
14
+ # distribution.
15
+ # * Neither the name of Google Inc. nor the names of its
16
+ # contributors may be used to endorse or promote products derived from
17
+ # this software without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ require "base64"
32
+ require "json"
33
+ require "monitor"
34
+ require "net/http"
35
+ require "openssl"
36
+
37
+ require "jwt"
38
+
39
+ module Google
40
+ module Auth
41
+ module IDTokens
42
+ ##
43
+ # A public key used for verifying ID tokens.
44
+ #
45
+ # This includes the public key data, ID, and the algorithm used for
46
+ # signature verification. RSA and Elliptical Curve (EC) keys are
47
+ # supported.
48
+ #
49
+ class KeyInfo
50
+ ##
51
+ # Create a public key info structure.
52
+ #
53
+ # @param id [String] The key ID.
54
+ # @param key [OpenSSL::PKey::RSA,OpenSSL::PKey::EC] The key itself.
55
+ # @param algorithm [String] The algorithm (normally `RS256` or `ES256`)
56
+ #
57
+ def initialize id: nil, key: nil, algorithm: nil
58
+ @id = id
59
+ @key = key
60
+ @algorithm = algorithm
61
+ end
62
+
63
+ ##
64
+ # The key ID.
65
+ # @return [String]
66
+ #
67
+ attr_reader :id
68
+
69
+ ##
70
+ # The key itself.
71
+ # @return [OpenSSL::PKey::RSA,OpenSSL::PKey::EC]
72
+ #
73
+ attr_reader :key
74
+
75
+ ##
76
+ # The signature algorithm. (normally `RS256` or `ES256`)
77
+ # @return [String]
78
+ #
79
+ attr_reader :algorithm
80
+
81
+ class << self
82
+ ##
83
+ # Create a KeyInfo from a single JWK, which may be given as either a
84
+ # hash or an unparsed JSON string.
85
+ #
86
+ # @param jwk [Hash,String] The JWK specification.
87
+ # @return [KeyInfo]
88
+ # @raise [KeySourceError] If the key could not be extracted from the
89
+ # JWK.
90
+ #
91
+ def from_jwk jwk
92
+ jwk = symbolize_keys ensure_json_parsed jwk
93
+ key = case jwk[:kty]
94
+ when "RSA"
95
+ extract_rsa_key jwk
96
+ when "EC"
97
+ extract_ec_key jwk
98
+ when nil
99
+ raise KeySourceError, "Key type not found"
100
+ else
101
+ raise KeySourceError, "Cannot use key type #{jwk[:kty]}"
102
+ end
103
+ new id: jwk[:kid], key: key, algorithm: jwk[:alg]
104
+ end
105
+
106
+ ##
107
+ # Create an array of KeyInfo from a JWK Set, which may be given as
108
+ # either a hash or an unparsed JSON string.
109
+ #
110
+ # @param jwk [Hash,String] The JWK Set specification.
111
+ # @return [Array<KeyInfo>]
112
+ # @raise [KeySourceError] If a key could not be extracted from the
113
+ # JWK Set.
114
+ #
115
+ def from_jwk_set jwk_set
116
+ jwk_set = symbolize_keys ensure_json_parsed jwk_set
117
+ jwks = jwk_set[:keys]
118
+ raise KeySourceError, "No keys found in jwk set" unless jwks
119
+ jwks.map { |jwk| from_jwk jwk }
120
+ end
121
+
122
+ private
123
+
124
+ def ensure_json_parsed input
125
+ return input unless input.is_a? String
126
+ JSON.parse input
127
+ rescue JSON::ParserError
128
+ raise KeySourceError, "Unable to parse JSON"
129
+ end
130
+
131
+ def symbolize_keys hash
132
+ result = {}
133
+ hash.each { |key, val| result[key.to_sym] = val }
134
+ result
135
+ end
136
+
137
+ def extract_rsa_key jwk
138
+ begin
139
+ n_data = Base64.urlsafe_decode64 jwk[:n]
140
+ e_data = Base64.urlsafe_decode64 jwk[:e]
141
+ rescue ArgumentError
142
+ raise KeySourceError, "Badly formatted key data"
143
+ end
144
+ n_bn = OpenSSL::BN.new n_data, 2
145
+ e_bn = OpenSSL::BN.new e_data, 2
146
+ rsa_key = OpenSSL::PKey::RSA.new
147
+ if rsa_key.respond_to? :set_key
148
+ rsa_key.set_key n_bn, e_bn, nil
149
+ else
150
+ rsa_key.n = n_bn
151
+ rsa_key.e = e_bn
152
+ end
153
+ rsa_key.public_key
154
+ end
155
+
156
+ # @private
157
+ CURVE_NAME_MAP = {
158
+ "P-256" => "prime256v1",
159
+ "P-384" => "secp384r1",
160
+ "P-521" => "secp521r1",
161
+ "secp256k1" => "secp256k1"
162
+ }.freeze
163
+
164
+ def extract_ec_key jwk
165
+ begin
166
+ x_data = Base64.urlsafe_decode64 jwk[:x]
167
+ y_data = Base64.urlsafe_decode64 jwk[:y]
168
+ rescue ArgumentError
169
+ raise KeySourceError, "Badly formatted key data"
170
+ end
171
+ curve_name = CURVE_NAME_MAP[jwk[:crv]]
172
+ raise KeySourceError, "Unsupported EC curve #{jwk[:crv]}" unless curve_name
173
+ group = OpenSSL::PKey::EC::Group.new curve_name
174
+ x_hex = x_data.unpack1 "H*"
175
+ y_hex = y_data.unpack1 "H*"
176
+ bn = OpenSSL::BN.new ["04#{x_hex}#{y_hex}"].pack("H*"), 2
177
+ key = OpenSSL::PKey::EC.new curve_name
178
+ key.public_key = OpenSSL::PKey::EC::Point.new group, bn
179
+ key
180
+ end
181
+ end
182
+ end
183
+
184
+ ##
185
+ # A key source that contains a static set of keys.
186
+ #
187
+ class StaticKeySource
188
+ ##
189
+ # Create a static key source with the given keys.
190
+ #
191
+ # @param keys [Array<KeyInfo>] The keys
192
+ #
193
+ def initialize keys
194
+ @current_keys = Array(keys)
195
+ end
196
+
197
+ ##
198
+ # Return the current keys. Does not perform any refresh.
199
+ #
200
+ # @return [Array<KeyInfo>]
201
+ #
202
+ attr_reader :current_keys
203
+ alias refresh_keys current_keys
204
+
205
+ class << self
206
+ ##
207
+ # Create a static key source containing a single key parsed from a
208
+ # single JWK, which may be given as either a hash or an unparsed
209
+ # JSON string.
210
+ #
211
+ # @param jwk [Hash,String] The JWK specification.
212
+ # @return [StaticKeySource]
213
+ #
214
+ def from_jwk jwk
215
+ new KeyInfo.from_jwk jwk
216
+ end
217
+
218
+ ##
219
+ # Create a static key source containing multiple keys parsed from a
220
+ # JWK Set, which may be given as either a hash or an unparsed JSON
221
+ # string.
222
+ #
223
+ # @param jwk_set [Hash,String] The JWK Set specification.
224
+ # @return [StaticKeySource]
225
+ #
226
+ def from_jwk_set jwk_set
227
+ new KeyInfo.from_jwk_set jwk_set
228
+ end
229
+ end
230
+ end
231
+
232
+ ##
233
+ # A base key source that downloads keys from a URI. Subclasses should
234
+ # override {HttpKeySource#interpret_json} to parse the response.
235
+ #
236
+ class HttpKeySource
237
+ ##
238
+ # The default interval between retries in seconds (3600s = 1hr).
239
+ #
240
+ # @return [Integer]
241
+ #
242
+ DEFAULT_RETRY_INTERVAL = 3600
243
+
244
+ ##
245
+ # Create an HTTP key source.
246
+ #
247
+ # @param uri [String,URI] The URI from which to download keys.
248
+ # @param retry_interval [Integer,nil] Override the retry interval in
249
+ # seconds. This is the minimum time between retries of failed key
250
+ # downloads.
251
+ #
252
+ def initialize uri, retry_interval: nil
253
+ @uri = URI uri
254
+ @retry_interval = retry_interval || DEFAULT_RETRY_INTERVAL
255
+ @allow_refresh_at = Time.now
256
+ @current_keys = []
257
+ @monitor = Monitor.new
258
+ end
259
+
260
+ ##
261
+ # The URI from which to download keys.
262
+ # @return [Array<KeyInfo>]
263
+ #
264
+ attr_reader :uri
265
+
266
+ ##
267
+ # Return the current keys, without attempting to re-download.
268
+ #
269
+ # @return [Array<KeyInfo>]
270
+ #
271
+ attr_reader :current_keys
272
+
273
+ ##
274
+ # Attempt to re-download keys (if the retry interval has expired) and
275
+ # return the new keys.
276
+ #
277
+ # @return [Array<KeyInfo>]
278
+ # @raise [KeySourceError] if key retrieval failed.
279
+ #
280
+ def refresh_keys
281
+ @monitor.synchronize do
282
+ return @current_keys if Time.now < @allow_refresh_at
283
+ @allow_refresh_at = Time.now + @retry_interval
284
+
285
+ response = Net::HTTP.get_response uri
286
+ raise KeySourceError, "Unable to retrieve data from #{uri}" unless response.is_a? Net::HTTPSuccess
287
+
288
+ data = begin
289
+ JSON.parse response.body
290
+ rescue JSON::ParserError
291
+ raise KeySourceError, "Unable to parse JSON"
292
+ end
293
+
294
+ @current_keys = Array(interpret_json(data))
295
+ end
296
+ end
297
+
298
+ protected
299
+
300
+ def interpret_json _data
301
+ nil
302
+ end
303
+ end
304
+
305
+ ##
306
+ # A key source that downloads X509 certificates.
307
+ # Used by the legacy OAuth V1 public certs endpoint.
308
+ #
309
+ class X509CertHttpKeySource < HttpKeySource
310
+ ##
311
+ # Create a key source that downloads X509 certificates.
312
+ #
313
+ # @param uri [String,URI] The URI from which to download keys.
314
+ # @param algorithm [String] The algorithm to use for signature
315
+ # verification. Defaults to "`RS256`".
316
+ # @param retry_interval [Integer,nil] Override the retry interval in
317
+ # seconds. This is the minimum time between retries of failed key
318
+ # downloads.
319
+ #
320
+ def initialize uri, algorithm: "RS256", retry_interval: nil
321
+ super uri, retry_interval: retry_interval
322
+ @algorithm = algorithm
323
+ end
324
+
325
+ protected
326
+
327
+ def interpret_json data
328
+ data.map do |id, cert_str|
329
+ key = OpenSSL::X509::Certificate.new(cert_str).public_key
330
+ KeyInfo.new id: id, key: key, algorithm: @algorithm
331
+ end
332
+ rescue OpenSSL::X509::CertificateError
333
+ raise KeySourceError, "Unable to parse X509 certificates"
334
+ end
335
+ end
336
+
337
+ ##
338
+ # A key source that downloads a JWK set.
339
+ #
340
+ class JwkHttpKeySource < HttpKeySource
341
+ ##
342
+ # Create a key source that downloads a JWT Set.
343
+ #
344
+ # @param uri [String,URI] The URI from which to download keys.
345
+ # @param retry_interval [Integer,nil] Override the retry interval in
346
+ # seconds. This is the minimum time between retries of failed key
347
+ # downloads.
348
+ #
349
+ def initialize uri, retry_interval: nil
350
+ super uri, retry_interval: retry_interval
351
+ end
352
+
353
+ protected
354
+
355
+ def interpret_json data
356
+ KeyInfo.from_jwk_set data
357
+ end
358
+ end
359
+
360
+ ##
361
+ # A key source that aggregates other key sources. This means it will
362
+ # aggregate the keys provided by its constituent sources. Additionally,
363
+ # when asked to refresh, it will refresh all its constituent sources.
364
+ #
365
+ class AggregateKeySource
366
+ ##
367
+ # Create a key source that aggregates other key sources.
368
+ #
369
+ # @param sources [Array<key source>] The key sources to aggregate.
370
+ #
371
+ def initialize sources
372
+ @sources = Array(sources)
373
+ end
374
+
375
+ ##
376
+ # Return the current keys, without attempting to refresh.
377
+ #
378
+ # @return [Array<KeyInfo>]
379
+ #
380
+ def current_keys
381
+ @sources.flat_map(&:current_keys)
382
+ end
383
+
384
+ ##
385
+ # Attempt to refresh keys and return the new keys.
386
+ #
387
+ # @return [Array<KeyInfo>]
388
+ # @raise [KeySourceError] if key retrieval failed.
389
+ #
390
+ def refresh_keys
391
+ @sources.flat_map(&:refresh_keys)
392
+ end
393
+ end
394
+ end
395
+ end
396
+ end