googleauth 1.9.2 → 1.10.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: '089cb78f4121da1e8502eb258e106d161b72642f1d4aeb9959ea605197dba5d9'
4
- data.tar.gz: a93423d64db3c8e0fb55807586befc3d13480c43e17b4a8cbd37704f95d12fa7
3
+ metadata.gz: 3298a8e70480c26df728476ce2bc2723b28f571e2ffd56b5915d6df70f4d2beb
4
+ data.tar.gz: 8121dbd23bcc3b7458f9eab0d83210321109b7006bac1f53ce94801e544f3ee9
5
5
  SHA512:
6
- metadata.gz: 1d3513b20df2ec263c7f7db1a14421d4fef63fdaf836cbad78c62b22bf8399bd6ed7a830ba8548ffa197e6a7279eb0f8aaab65379c7cf1b4612a494e9a83a8ec
7
- data.tar.gz: 8ee1b859360348eded906242f44e7a7d58841a864e195f34b0726c2aba660e6c10ae07e27d3d2a4517e1756b5ff989e449aad0965419995b4f2bc58ad45ae597
6
+ metadata.gz: 779e31aafd092dc978ba405171502011d8b4828f614c8023e6fd7d8296fd6ec805650187cabade3b8b4528c5020ff1455f38b1db8d3b4da82c6a76a0a400f3fc
7
+ data.tar.gz: 51ea4d44575fc2f778d60c702c370d53c2f36b42f67c046c212fbe7a97ac9dc4da54f08a8d7fffd144a8b60e2db465233f22d3a2f318ea0814aaf42f05989aa8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Release History
2
2
 
3
+ ### 1.10.0 (2024-02-08)
4
+
5
+ #### Features
6
+
7
+ * add PKCE to 3 Legged OAuth exchange ([#471](https://github.com/googleapis/google-auth-library-ruby/issues/471))
8
+ #### Bug Fixes
9
+
10
+ * Client library credentials provide correct self-signed JWT and external account behavior when loading from a file path or JSON data ([#474](https://github.com/googleapis/google-auth-library-ruby/issues/474))
11
+ * Prioritize universe domain specified in GCECredentials arguments over metadata-fetched value ([#472](https://github.com/googleapis/google-auth-library-ruby/issues/472))
12
+
3
13
  ### 1.9.2 (2024-01-25)
4
14
 
5
15
  #### Bug Fixes
data/README.md CHANGED
@@ -97,6 +97,45 @@ get('/oauth2callback') do
97
97
  end
98
98
  ```
99
99
 
100
+ ### Example (Web with PKCE)
101
+
102
+ Proof Key for Code Exchange (PKCE) is an [RFC](https://www.rfc-editor.org/rfc/rfc7636) that aims to prevent malicious operating system processes from hijacking an OAUTH 2.0 exchange. PKCE mitigates the above vulnerability by including `code_challenge` and `code_challenge_method` parameters in the Authorization Request and a `code_verifier` parameter in the Access Token Request.
103
+
104
+ ```ruby
105
+ require 'googleauth'
106
+ require 'googleauth/web_user_authorizer'
107
+ require 'googleauth/stores/redis_token_store'
108
+ require 'redis'
109
+
110
+ client_id = Google::Auth::ClientId.from_file('/path/to/client_secrets.json')
111
+ scope = ['https://www.googleapis.com/auth/drive']
112
+ token_store = Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new)
113
+ authorizer = Google::Auth::WebUserAuthorizer.new(
114
+ client_id, scope, token_store, '/oauth2callback')
115
+
116
+
117
+ get('/authorize') do
118
+ # NOTE: Assumes the user is already authenticated to the app
119
+ user_id = request.session['user_id']
120
+ # User needs to take care of generating the code_verifier and storing it in
121
+ # the session.
122
+ request.session['code_verifier'] ||= Google::Auth::WebUserAuthorizer.generate_code_verifier
123
+ authorizer.code_verifier = request.session['code_verifier']
124
+ credentials = authorizer.get_credentials(user_id, request)
125
+ if credentials.nil?
126
+ redirect authorizer.get_authorization_url(login_hint: user_id, request: request)
127
+ end
128
+ # Credentials are valid, can call APIs
129
+ # ...
130
+ end
131
+
132
+ get('/oauth2callback') do
133
+ target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(
134
+ request)
135
+ redirect target_url
136
+ end
137
+ ```
138
+
100
139
  ### Example (Command Line) [Deprecated]
101
140
 
102
141
  The Google Auth OOB flow has been discontiued on January 31, 2023. The OOB flow is a legacy flow that is no longer considered secure. To continue using Google Auth, please migrate your applications to a more secure flow. For more information on how to do this, please refer to this [OOB Migration](https://developers.google.com/identity/protocols/oauth2/resources/oob-migration) guide.
@@ -80,6 +80,14 @@ module Google
80
80
  alias unmemoize_all reset_cache
81
81
  end
82
82
 
83
+ # Construct a GCECredentials
84
+ def initialize options = {}
85
+ # Override the constructor to remember whether the universe domain was
86
+ # overridden by a constructor argument.
87
+ @universe_domain_overridden = options["universe_domain"] || options[:universe_domain] ? true : false
88
+ super options
89
+ end
90
+
83
91
  # Overrides the super class method to change how access tokens are
84
92
  # fetched.
85
93
  def fetch_access_token _options = {}
@@ -119,9 +127,11 @@ module Google
119
127
  else
120
128
  Signet::OAuth2.parse_credentials body, content_type
121
129
  end
122
- universe_domain = Google::Cloud.env.lookup_metadata "universe", "universe_domain"
123
- universe_domain = "googleapis.com" if !universe_domain || universe_domain.empty?
124
- hash["universe_domain"] = universe_domain.strip
130
+ unless @universe_domain_overridden
131
+ universe_domain = Google::Cloud.env.lookup_metadata "universe", "universe_domain"
132
+ universe_domain = "googleapis.com" if !universe_domain || universe_domain.empty?
133
+ hash["universe_domain"] = universe_domain.strip
134
+ end
125
135
  # The response might have been cached, which means expires_in might be
126
136
  # stale. Update it based on the time since the data was retrieved.
127
137
  # We also ensure expires_in is conservative; subtracting at least 1
@@ -356,8 +356,9 @@ module Google
356
356
  #
357
357
  def initialize keyfile, options = {}
358
358
  verify_keyfile_provided! keyfile
359
- @project_id = options["project_id"] || options["project"]
360
- @quota_project_id = options["quota_project_id"]
359
+ options = symbolize_hash_keys options
360
+ @project_id = options[:project_id] || options[:project]
361
+ @quota_project_id = options[:quota_project_id]
361
362
  case keyfile
362
363
  when Google::Auth::BaseClient
363
364
  update_from_signet keyfile
@@ -484,10 +485,11 @@ module Google
484
485
  end
485
486
 
486
487
  # Initializes the Signet client.
487
- def init_client keyfile, connection_options = {}
488
- client_opts = client_options keyfile
489
- Signet::OAuth2::Client.new(client_opts)
490
- .configure_connection(connection_options)
488
+ def init_client hash, options = {}
489
+ options = update_client_options options
490
+ io = StringIO.new JSON.generate hash
491
+ options.merge! json_key_io: io
492
+ Google::Auth::DefaultCredentials.make_creds options
491
493
  end
492
494
 
493
495
  # returns a new Hash with string keys instead of symbol keys.
@@ -495,34 +497,28 @@ module Google
495
497
  hash.to_h.transform_keys(&:to_s)
496
498
  end
497
499
 
498
- # rubocop:disable Metrics/AbcSize
500
+ # returns a new Hash with symbol keys instead of string keys.
501
+ def symbolize_hash_keys hash
502
+ hash.to_h.transform_keys(&:to_sym)
503
+ end
504
+
505
+ def update_client_options options
506
+ options = options.dup
499
507
 
500
- def client_options options
501
- # Keyfile options have higher priority over constructor defaults
502
- options["token_credential_uri"] ||= self.class.token_credential_uri
503
- options["audience"] ||= self.class.audience
504
- options["scope"] ||= self.class.scope
505
- options["target_audience"] ||= self.class.target_audience
508
+ # options have higher priority over constructor defaults
509
+ options[:token_credential_uri] ||= self.class.token_credential_uri
510
+ options[:audience] ||= self.class.audience
511
+ options[:scope] ||= self.class.scope
512
+ options[:target_audience] ||= self.class.target_audience
506
513
 
507
- if !Array(options["scope"]).empty? && options["target_audience"]
514
+ if !Array(options[:scope]).empty? && options[:target_audience]
508
515
  raise ArgumentError, "Cannot specify both scope and target_audience"
509
516
  end
517
+ options.delete :scope unless options[:target_audience].nil?
510
518
 
511
- needs_scope = options["target_audience"].nil?
512
- # client options for initializing signet client
513
- {
514
- token_credential_uri: options["token_credential_uri"],
515
- audience: options["audience"],
516
- scope: (needs_scope ? Array(options["scope"]) : nil),
517
- target_audience: options["target_audience"],
518
- issuer: options["client_email"],
519
- signing_key: OpenSSL::PKey::RSA.new(options["private_key"]),
520
- universe_domain: options["universe_domain"] || "googleapis.com"
521
- }
519
+ options
522
520
  end
523
521
 
524
- # rubocop:enable Metrics/AbcSize
525
-
526
522
  def update_from_signet client
527
523
  @project_id ||= client.project_id if client.respond_to? :project_id
528
524
  @quota_project_id ||= client.quota_project_id if client.respond_to? :quota_project_id
@@ -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
@@ -57,7 +58,11 @@ module Google
57
58
  # @param [String] callback_uri
58
59
  # URL (either absolute or relative) of the auth callback.
59
60
  # Defaults to '/oauth2callback'
60
- def initialize client_id, scope, token_store, callback_uri = nil
61
+ # @param [String] code_verifier
62
+ # Random string of 43-128 chars used to verify the key exchange using
63
+ # PKCE.
64
+ def initialize client_id, scope, token_store,
65
+ callback_uri = nil, code_verifier: nil
61
66
  raise NIL_CLIENT_ID_ERROR if client_id.nil?
62
67
  raise NIL_SCOPE_ERROR if scope.nil?
63
68
 
@@ -65,6 +70,7 @@ module Google
65
70
  @scope = Array(scope)
66
71
  @token_store = token_store
67
72
  @callback_uri = callback_uri || "/oauth2callback"
73
+ @code_verifier = code_verifier
68
74
  end
69
75
 
70
76
  # Build the URL for requesting authorization.
@@ -86,6 +92,18 @@ module Google
86
92
  # Authorization url
87
93
  def get_authorization_url options = {}
88
94
  scope = options[:scope] || @scope
95
+
96
+ options[:additional_parameters] ||= {}
97
+
98
+ if @code_verifier
99
+ options[:additional_parameters].merge!(
100
+ {
101
+ code_challenge: generate_code_challenge(@code_verifier),
102
+ code_challenge_method: code_challenge_method
103
+ }
104
+ )
105
+ end
106
+
89
107
  credentials = UserRefreshCredentials.new(
90
108
  client_id: @client_id.id,
91
109
  client_secret: @client_id.secret,
@@ -157,6 +175,8 @@ module Google
157
175
  code = options[:code]
158
176
  scope = options[:scope] || @scope
159
177
  base_url = options[:base_url]
178
+ options[:additional_parameters] ||= {}
179
+ options[:additional_parameters].merge!({ code_verifier: @code_verifier })
160
180
  credentials = UserRefreshCredentials.new(
161
181
  client_id: @client_id.id,
162
182
  client_secret: @client_id.secret,
@@ -228,6 +248,23 @@ module Google
228
248
  credentials
229
249
  end
230
250
 
251
+ # The code verifier for PKCE for OAuth 2.0. When set, the
252
+ # authorization URI will contain the Code Challenge and Code
253
+ # Challenge Method querystring parameters, and the token URI will
254
+ # contain the Code Verifier parameter.
255
+ #
256
+ # @param [String|nil] new_code_erifier
257
+ def code_verifier= new_code_verifier
258
+ @code_verifier = new_code_verifier
259
+ end
260
+
261
+ # Generate the code verifier needed to be sent while fetching
262
+ # authorization URL.
263
+ def self.generate_code_verifier
264
+ random_number = rand 32..96
265
+ SecureRandom.alphanumeric random_number
266
+ end
267
+
231
268
  private
232
269
 
233
270
  # @private Fetch stored token with given user_id
@@ -272,6 +309,15 @@ module Google
272
309
  def uri_is_postmessage? uri
273
310
  uri.to_s.casecmp("postmessage").zero?
274
311
  end
312
+
313
+ def generate_code_challenge code_verifier
314
+ digest = Digest::SHA256.digest code_verifier
315
+ Base64.urlsafe_encode64 digest, padding: false
316
+ end
317
+
318
+ def code_challenge_method
319
+ "S256"
320
+ end
275
321
  end
276
322
  end
277
323
  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.9.2".freeze
19
+ VERSION = "1.10.0".freeze
20
20
  end
21
21
  end
@@ -96,8 +96,12 @@ module Google
96
96
  # @param [String] callback_uri
97
97
  # URL (either absolute or relative) of the auth callback. Defaults
98
98
  # to '/oauth2callback'
99
- def initialize client_id, scope, token_store, callback_uri = nil
100
- super client_id, scope, token_store, callback_uri
99
+ # @param [String] code_verifier
100
+ # Random string of 43-128 chars used to verify the key exchange using
101
+ # PKCE.
102
+ def initialize client_id, scope, token_store,
103
+ callback_uri = nil, code_verifier: nil
104
+ super client_id, scope, token_store, callback_uri, code_verifier: code_verifier
101
105
  end
102
106
 
103
107
  # Handle the result of the oauth callback. Exchanges the authorization
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: 1.9.2
4
+ version: 1.10.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: 2024-01-25 00:00:00.000000000 Z
11
+ date: 2024-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday