googleauth 1.9.1 → 1.11.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 +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +39 -0
- data/lib/googleauth/compute_engine.rb +13 -3
- data/lib/googleauth/credentials.rb +23 -27
- data/lib/googleauth/service_account.rb +16 -6
- data/lib/googleauth/user_authorizer.rb +54 -4
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +15 -4
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4912a601c0a234fa9faf150d7461cab993f775b715f6ac7d19db017ceae74e6d
|
4
|
+
data.tar.gz: e08da21e12d58260944079d068a04099fa0c812eb486e760a3ab12dd002700f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
360
|
-
@
|
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
|
488
|
-
|
489
|
-
|
490
|
-
|
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
|
-
#
|
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
|
-
|
501
|
-
|
502
|
-
options[
|
503
|
-
options[
|
504
|
-
options[
|
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[
|
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
|
-
|
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
|
-
|
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
|
-
|
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]
|
58
|
+
# @param [String] legacy_callback_uri
|
58
59
|
# URL (either absolute or relative) of the auth callback.
|
59
|
-
# Defaults to '/oauth2callback'
|
60
|
-
|
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
|
data/lib/googleauth/version.rb
CHANGED
@@ -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]
|
96
|
+
# @param [String] legacy_callback_uri
|
97
97
|
# URL (either absolute or relative) of the auth callback. Defaults
|
98
|
-
# to '/oauth2callback'
|
99
|
-
|
100
|
-
|
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.
|
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:
|
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.
|
189
|
+
rubygems_version: 3.5.3
|
190
190
|
signing_key:
|
191
191
|
specification_version: 4
|
192
192
|
summary: Google Auth Library for Ruby
|