googleauth 1.9.1 → 1.11.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: 5950bbb2b0bb696d85ded5cfd31bc2a44192b9a5e42c774260300e467a821316
4
- data.tar.gz: 937e9ef7634d2667a6e552276e0dc7716511a3597b33de2c20174a865bcb3598
3
+ metadata.gz: 4912a601c0a234fa9faf150d7461cab993f775b715f6ac7d19db017ceae74e6d
4
+ data.tar.gz: e08da21e12d58260944079d068a04099fa0c812eb486e760a3ab12dd002700f4
5
5
  SHA512:
6
- metadata.gz: 94c41b28ce5fdd2ed5437cf30aeb67d53f04b5863711fd74b1403612cd0747f9e2856fda14678927ef1c026af0f45e6c15a2eef68415a77585b752845f7852a6
7
- data.tar.gz: 46d4ace82973f0df463733bad339ea00786baa90e1adbb665bdadf3d71999dfeb67177f98b4b26b13464307d02a5ef7b649793bb56dc7619b7f9a076118b3942
6
+ metadata.gz: b0346fcaf38cb783fd4d22f0734994298d63dfa1a89fd34df4d4f42b87160de410e34c93ab4773c3bbaf03b41160f10e3fd5bc0fa137d1ab6fb5dce15f72ba53
7
+ data.tar.gz: c5ff10a04491e9f56dff9bcea24c67398e67713efc89c2d378075621da837b59c5fdbd36c0b97d5753fd0358befe8ce2ceacc86af1cce0a1c54d609d916903c6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Release History
2
2
 
3
+ ### 1.11.0 (2024-02-09)
4
+
5
+ #### Features
6
+
7
+ * Deprecate the positional argument for callback_uri, and introduce keyword argument instead ([#475](https://github.com/googleapis/google-auth-library-ruby/issues/475))
8
+
9
+ ### 1.10.0 (2024-02-08)
10
+
11
+ #### Features
12
+
13
+ * add PKCE to 3 Legged OAuth exchange ([#471](https://github.com/googleapis/google-auth-library-ruby/issues/471))
14
+ #### Bug Fixes
15
+
16
+ * 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))
17
+ * Prioritize universe domain specified in GCECredentials arguments over metadata-fetched value ([#472](https://github.com/googleapis/google-auth-library-ruby/issues/472))
18
+
19
+ ### 1.9.2 (2024-01-25)
20
+
21
+ #### Bug Fixes
22
+
23
+ * Prevent access tokens from being fetched at service account construction in the self-signed-jwt case ([#467](https://github.com/googleapis/google-auth-library-ruby/issues/467))
24
+
3
25
  ### 1.9.1 (2023-12-12)
4
26
 
5
27
  #### 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
@@ -39,7 +39,11 @@ module Google
39
39
  attr_reader :quota_project_id
40
40
 
41
41
  def enable_self_signed_jwt?
42
- @enable_self_signed_jwt
42
+ # Use a self-singed JWT if there's no information that can be used to
43
+ # obtain an OAuth token, OR if there are scopes but also an assertion
44
+ # that they are default scopes that shouldn't be used to fetch a token,
45
+ # OR we are not in the default universe and thus OAuth isn't supported.
46
+ target_audience.nil? && (scope.nil? || @enable_self_signed_jwt || universe_domain != "googleapis.com")
43
47
  end
44
48
 
45
49
  # Creates a ServiceAccountCredentials.
@@ -95,17 +99,18 @@ module Google
95
99
  # Extends the base class to use a transient
96
100
  # ServiceAccountJwtHeaderCredentials for certain cases.
97
101
  def apply! a_hash, opts = {}
98
- # Use a self-singed JWT if there's no information that can be used to
99
- # obtain an OAuth token, OR if there are scopes but also an assertion
100
- # that they are default scopes that shouldn't be used to fetch a token,
101
- # OR we are not in the default universe and thus OAuth isn't supported.
102
- if target_audience.nil? && (scope.nil? || enable_self_signed_jwt? || universe_domain != "googleapis.com")
102
+ if enable_self_signed_jwt?
103
103
  apply_self_signed_jwt! a_hash
104
104
  else
105
105
  super
106
106
  end
107
107
  end
108
108
 
109
+ # Modifies this logic so it also requires self-signed-jwt to be disabled
110
+ def needs_access_token?
111
+ super && !enable_self_signed_jwt?
112
+ end
113
+
109
114
  private
110
115
 
111
116
  def apply_self_signed_jwt! a_hash
@@ -216,6 +221,11 @@ module Google
216
221
 
217
222
  JWT.encode assertion, @signing_key, SIGNING_ALGORITHM
218
223
  end
224
+
225
+ # Duck-types the corresponding method from BaseClient
226
+ def needs_access_token?
227
+ false
228
+ end
219
229
  end
220
230
  end
221
231
  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,26 @@ 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
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
+ def initialize client_id, scope, token_store,
67
+ legacy_callback_uri = nil,
68
+ callback_uri: nil,
69
+ code_verifier: nil
61
70
  raise NIL_CLIENT_ID_ERROR if client_id.nil?
62
71
  raise NIL_SCOPE_ERROR if scope.nil?
63
72
 
64
73
  @client_id = client_id
65
74
  @scope = Array(scope)
66
75
  @token_store = token_store
67
- @callback_uri = callback_uri || "/oauth2callback"
76
+ @callback_uri = legacy_callback_uri || callback_uri || "/oauth2callback"
77
+ @code_verifier = code_verifier
68
78
  end
69
79
 
70
80
  # Build the URL for requesting authorization.
@@ -86,6 +96,18 @@ module Google
86
96
  # Authorization url
87
97
  def get_authorization_url options = {}
88
98
  scope = options[:scope] || @scope
99
+
100
+ options[:additional_parameters] ||= {}
101
+
102
+ if @code_verifier
103
+ options[:additional_parameters].merge!(
104
+ {
105
+ code_challenge: generate_code_challenge(@code_verifier),
106
+ code_challenge_method: code_challenge_method
107
+ }
108
+ )
109
+ end
110
+
89
111
  credentials = UserRefreshCredentials.new(
90
112
  client_id: @client_id.id,
91
113
  client_secret: @client_id.secret,
@@ -157,6 +179,8 @@ module Google
157
179
  code = options[:code]
158
180
  scope = options[:scope] || @scope
159
181
  base_url = options[:base_url]
182
+ options[:additional_parameters] ||= {}
183
+ options[:additional_parameters].merge!({ code_verifier: @code_verifier })
160
184
  credentials = UserRefreshCredentials.new(
161
185
  client_id: @client_id.id,
162
186
  client_secret: @client_id.secret,
@@ -228,6 +252,23 @@ module Google
228
252
  credentials
229
253
  end
230
254
 
255
+ # The code verifier for PKCE for OAuth 2.0. When set, the
256
+ # authorization URI will contain the Code Challenge and Code
257
+ # Challenge Method querystring parameters, and the token URI will
258
+ # contain the Code Verifier parameter.
259
+ #
260
+ # @param [String|nil] new_code_erifier
261
+ def code_verifier= new_code_verifier
262
+ @code_verifier = new_code_verifier
263
+ end
264
+
265
+ # Generate the code verifier needed to be sent while fetching
266
+ # authorization URL.
267
+ def self.generate_code_verifier
268
+ random_number = rand 32..96
269
+ SecureRandom.alphanumeric random_number
270
+ end
271
+
231
272
  private
232
273
 
233
274
  # @private Fetch stored token with given user_id
@@ -272,6 +313,15 @@ module Google
272
313
  def uri_is_postmessage? uri
273
314
  uri.to_s.casecmp("postmessage").zero?
274
315
  end
316
+
317
+ def generate_code_challenge code_verifier
318
+ digest = Digest::SHA256.digest code_verifier
319
+ Base64.urlsafe_encode64 digest, padding: false
320
+ end
321
+
322
+ def code_challenge_method
323
+ "S256"
324
+ end
275
325
  end
276
326
  end
277
327
  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.1".freeze
19
+ VERSION = "1.11.0".freeze
20
20
  end
21
21
  end
@@ -93,11 +93,22 @@ module Google
93
93
  # Authorization scope to request
94
94
  # @param [Google::Auth::Stores::TokenStore] token_store
95
95
  # Backing storage for persisting user credentials
96
- # @param [String] callback_uri
96
+ # @param [String] legacy_callback_uri
97
97
  # URL (either absolute or relative) of the auth callback. Defaults
98
- # to '/oauth2callback'
99
- def initialize client_id, scope, token_store, callback_uri = nil
100
- super client_id, scope, token_store, callback_uri
98
+ # to '/oauth2callback'.
99
+ # @deprecated This field is deprecated. Instead, use the keyword
100
+ # argument callback_uri.
101
+ # @param [String] code_verifier
102
+ # Random string of 43-128 chars used to verify the key exchange using
103
+ # PKCE.
104
+ def initialize client_id, scope, token_store,
105
+ legacy_callback_uri = nil,
106
+ callback_uri: nil,
107
+ code_verifier: nil
108
+ super client_id, scope, token_store,
109
+ legacy_callback_uri,
110
+ code_verifier: code_verifier,
111
+ callback_uri: callback_uri
101
112
  end
102
113
 
103
114
  # 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.1
4
+ version: 1.11.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: 2023-12-12 00:00:00.000000000 Z
11
+ date: 2024-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -186,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
186
186
  - !ruby/object:Gem::Version
187
187
  version: '0'
188
188
  requirements: []
189
- rubygems_version: 3.4.19
189
+ rubygems_version: 3.5.3
190
190
  signing_key:
191
191
  specification_version: 4
192
192
  summary: Google Auth Library for Ruby