googleauth 1.8.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 +35 -0
- data/README.md +39 -0
- data/lib/googleauth/application_default.rb +1 -5
- data/lib/googleauth/compute_engine.rb +62 -40
- data/lib/googleauth/credentials.rb +31 -28
- data/lib/googleauth/external_account/base_credentials.rb +3 -2
- data/lib/googleauth/external_account.rb +2 -1
- data/lib/googleauth/json_key_reader.rb +2 -1
- data/lib/googleauth/service_account.rb +24 -8
- data/lib/googleauth/signet.rb +12 -0
- data/lib/googleauth/user_authorizer.rb +54 -4
- data/lib/googleauth/user_refresh.rb +4 -2
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +15 -4
- metadata +20 -6
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,40 @@
|
|
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
|
+
|
25
|
+
### 1.9.1 (2023-12-12)
|
26
|
+
|
27
|
+
#### Bug Fixes
|
28
|
+
|
29
|
+
* update expires_in for cached metadata-retrieved tokens ([#464](https://github.com/googleapis/google-auth-library-ruby/issues/464))
|
30
|
+
|
31
|
+
### 1.9.0 (2023-12-07)
|
32
|
+
|
33
|
+
#### Features
|
34
|
+
|
35
|
+
* Include universe_domain in credentials ([#460](https://github.com/googleapis/google-auth-library-ruby/issues/460))
|
36
|
+
* Use google-cloud-env for more robust Metadata Service access ([#459](https://github.com/googleapis/google-auth-library-ruby/issues/459))
|
37
|
+
|
3
38
|
### 1.8.1 (2023-09-19)
|
4
39
|
|
5
40
|
#### Documentation
|
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.
|
@@ -55,11 +55,7 @@ module Google
|
|
55
55
|
DefaultCredentials.from_well_known_path(scope, options) ||
|
56
56
|
DefaultCredentials.from_system_default_path(scope, options)
|
57
57
|
return creds unless creds.nil?
|
58
|
-
unless GCECredentials.on_gce? options
|
59
|
-
# Clear cache of the result of GCECredentials.on_gce?
|
60
|
-
GCECredentials.reset_cache
|
61
|
-
raise NOT_FOUND_ERROR
|
62
|
-
end
|
58
|
+
raise NOT_FOUND_ERROR unless GCECredentials.on_gce? options
|
63
59
|
GCECredentials.new options.merge(scope: scope)
|
64
60
|
end
|
65
61
|
end
|
@@ -12,7 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
require "
|
15
|
+
require "google-cloud-env"
|
16
16
|
require "googleauth/signet"
|
17
17
|
|
18
18
|
module Google
|
@@ -33,83 +33,77 @@ module Google
|
|
33
33
|
# Extends Signet::OAuth2::Client so that the auth token is obtained from
|
34
34
|
# the GCE metadata server.
|
35
35
|
class GCECredentials < Signet::OAuth2::Client
|
36
|
-
#
|
37
|
-
# systems.
|
36
|
+
# @private Unused and deprecated but retained to prevent breaking changes
|
38
37
|
DEFAULT_METADATA_HOST = "169.254.169.254".freeze
|
39
38
|
|
40
|
-
# @private Unused and deprecated
|
39
|
+
# @private Unused and deprecated but retained to prevent breaking changes
|
41
40
|
COMPUTE_AUTH_TOKEN_URI =
|
42
41
|
"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
|
43
|
-
# @private Unused and deprecated
|
42
|
+
# @private Unused and deprecated but retained to prevent breaking changes
|
44
43
|
COMPUTE_ID_TOKEN_URI =
|
45
44
|
"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity".freeze
|
46
|
-
# @private Unused and deprecated
|
45
|
+
# @private Unused and deprecated but retained to prevent breaking changes
|
47
46
|
COMPUTE_CHECK_URI = "http://169.254.169.254".freeze
|
48
47
|
|
49
|
-
@on_gce_cache = {}
|
50
|
-
|
51
48
|
class << self
|
49
|
+
# @private Unused and deprecated
|
52
50
|
def metadata_host
|
53
51
|
ENV.fetch "GCE_METADATA_HOST", DEFAULT_METADATA_HOST
|
54
52
|
end
|
55
53
|
|
54
|
+
# @private Unused and deprecated
|
56
55
|
def compute_check_uri
|
57
56
|
"http://#{metadata_host}".freeze
|
58
57
|
end
|
59
58
|
|
59
|
+
# @private Unused and deprecated
|
60
60
|
def compute_auth_token_uri
|
61
61
|
"#{compute_check_uri}/computeMetadata/v1/instance/service-accounts/default/token".freeze
|
62
62
|
end
|
63
63
|
|
64
|
+
# @private Unused and deprecated
|
64
65
|
def compute_id_token_uri
|
65
66
|
"#{compute_check_uri}/computeMetadata/v1/instance/service-accounts/default/identity".freeze
|
66
67
|
end
|
67
68
|
|
68
69
|
# Detect if this appear to be a GCE instance, by checking if metadata
|
69
70
|
# is available.
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
@on_gce_cache.fetch options do
|
74
|
-
@on_gce_cache[options] = begin
|
75
|
-
# TODO: This should use google-cloud-env instead.
|
76
|
-
c = options[:connection] || Faraday.default_connection
|
77
|
-
headers = { "Metadata-Flavor" => "Google" }
|
78
|
-
resp = c.get compute_check_uri, nil, headers do |req|
|
79
|
-
req.options.timeout = 1.0
|
80
|
-
req.options.open_timeout = 0.1
|
81
|
-
end
|
82
|
-
return false unless resp.status == 200
|
83
|
-
resp.headers["Metadata-Flavor"] == "Google"
|
84
|
-
rescue Faraday::TimeoutError, Faraday::ConnectionFailed
|
85
|
-
false
|
86
|
-
end
|
87
|
-
end
|
71
|
+
# The parameters are deprecated and unused.
|
72
|
+
def on_gce? _options = {}, _reload = false # rubocop:disable Style/OptionalBooleanParameter
|
73
|
+
Google::Cloud.env.metadata?
|
88
74
|
end
|
89
75
|
|
90
76
|
def reset_cache
|
91
|
-
|
77
|
+
Google::Cloud.env.compute_metadata.reset_existence!
|
78
|
+
Google::Cloud.env.compute_metadata.cache.expire_all!
|
92
79
|
end
|
93
80
|
alias unmemoize_all reset_cache
|
94
81
|
end
|
95
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
|
+
|
96
91
|
# Overrides the super class method to change how access tokens are
|
97
92
|
# fetched.
|
98
|
-
def fetch_access_token
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
query
|
104
|
-
|
93
|
+
def fetch_access_token _options = {}
|
94
|
+
if token_type == :id_token
|
95
|
+
query = { "audience" => target_audience, "format" => "full" }
|
96
|
+
entry = "service-accounts/default/identity"
|
97
|
+
else
|
98
|
+
query = {}
|
99
|
+
entry = "service-accounts/default/token"
|
100
|
+
end
|
101
|
+
query[:scopes] = Array(scope).join "," if scope
|
102
|
+
begin
|
103
|
+
resp = Google::Cloud.env.lookup_metadata_response "instance", entry, query: query
|
105
104
|
case resp.status
|
106
105
|
when 200
|
107
|
-
|
108
|
-
if ["text/html", "application/text"].include? content_type
|
109
|
-
{ (target_audience ? "id_token" : "access_token") => resp.body }
|
110
|
-
else
|
111
|
-
Signet::OAuth2.parse_credentials resp.body, content_type
|
112
|
-
end
|
106
|
+
build_token_hash resp.body, resp.headers["content-type"], resp.retrieval_monotonic_time
|
113
107
|
when 403, 500
|
114
108
|
msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
|
115
109
|
raise Signet::UnexpectedStatusError, msg
|
@@ -119,7 +113,35 @@ module Google
|
|
119
113
|
msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
|
120
114
|
raise Signet::AuthorizationError, msg
|
121
115
|
end
|
116
|
+
rescue Google::Cloud::Env::MetadataServerNotResponding => e
|
117
|
+
raise Signet::AuthorizationError, e.message
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def build_token_hash body, content_type, retrieval_time
|
124
|
+
hash =
|
125
|
+
if ["text/html", "application/text"].include? content_type
|
126
|
+
{ token_type.to_s => body }
|
127
|
+
else
|
128
|
+
Signet::OAuth2.parse_credentials body, content_type
|
129
|
+
end
|
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
|
135
|
+
# The response might have been cached, which means expires_in might be
|
136
|
+
# stale. Update it based on the time since the data was retrieved.
|
137
|
+
# We also ensure expires_in is conservative; subtracting at least 1
|
138
|
+
# second to offset any skew from metadata server latency.
|
139
|
+
if hash["expires_in"].is_a? Numeric
|
140
|
+
offset = 1 + (Process.clock_gettime(Process::CLOCK_MONOTONIC) - retrieval_time).round
|
141
|
+
hash["expires_in"] -= offset if offset.positive?
|
142
|
+
hash["expires_in"] = 0 if hash["expires_in"].negative?
|
122
143
|
end
|
144
|
+
hash
|
123
145
|
end
|
124
146
|
end
|
125
147
|
end
|
@@ -259,7 +259,7 @@ module Google
|
|
259
259
|
# @return [Object] The value
|
260
260
|
#
|
261
261
|
def self.lookup_auth_param name, method_name = name
|
262
|
-
val = instance_variable_get "@#{name}"
|
262
|
+
val = instance_variable_get :"@#{name}"
|
263
263
|
val = yield if val.nil? && block_given?
|
264
264
|
return val unless val.nil?
|
265
265
|
return superclass.send method_name if superclass.respond_to? method_name
|
@@ -328,9 +328,13 @@ module Google
|
|
328
328
|
# @return [Proc] Returns a reference to the {Signet::OAuth2::Client#apply} method,
|
329
329
|
# suitable for passing as a closure.
|
330
330
|
#
|
331
|
+
# @!attribute [rw] universe_domain
|
332
|
+
# @return [String] The universe domain issuing these credentials.
|
333
|
+
#
|
331
334
|
def_delegators :@client,
|
332
335
|
:token_credential_uri, :audience,
|
333
|
-
:scope, :issuer, :signing_key, :updater_proc, :target_audience
|
336
|
+
:scope, :issuer, :signing_key, :updater_proc, :target_audience,
|
337
|
+
:universe_domain, :universe_domain=
|
334
338
|
|
335
339
|
##
|
336
340
|
# Creates a new Credentials instance with the provided auth credentials, and with the default
|
@@ -352,8 +356,9 @@ module Google
|
|
352
356
|
#
|
353
357
|
def initialize keyfile, options = {}
|
354
358
|
verify_keyfile_provided! keyfile
|
355
|
-
|
356
|
-
@
|
359
|
+
options = symbolize_hash_keys options
|
360
|
+
@project_id = options[:project_id] || options[:project]
|
361
|
+
@quota_project_id = options[:quota_project_id]
|
357
362
|
case keyfile
|
358
363
|
when Google::Auth::BaseClient
|
359
364
|
update_from_signet keyfile
|
@@ -480,10 +485,11 @@ module Google
|
|
480
485
|
end
|
481
486
|
|
482
487
|
# Initializes the Signet client.
|
483
|
-
def init_client
|
484
|
-
|
485
|
-
|
486
|
-
|
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
|
487
493
|
end
|
488
494
|
|
489
495
|
# returns a new Hash with string keys instead of symbol keys.
|
@@ -491,31 +497,28 @@ module Google
|
|
491
497
|
hash.to_h.transform_keys(&:to_s)
|
492
498
|
end
|
493
499
|
|
494
|
-
#
|
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
|
495
507
|
|
496
|
-
|
497
|
-
|
498
|
-
options[
|
499
|
-
options[
|
500
|
-
options[
|
501
|
-
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
|
502
513
|
|
503
|
-
if !Array(options[
|
514
|
+
if !Array(options[:scope]).empty? && options[:target_audience]
|
504
515
|
raise ArgumentError, "Cannot specify both scope and target_audience"
|
505
516
|
end
|
517
|
+
options.delete :scope unless options[:target_audience].nil?
|
506
518
|
|
507
|
-
|
508
|
-
# client options for initializing signet client
|
509
|
-
{ token_credential_uri: options["token_credential_uri"],
|
510
|
-
audience: options["audience"],
|
511
|
-
scope: (needs_scope ? Array(options["scope"]) : nil),
|
512
|
-
target_audience: options["target_audience"],
|
513
|
-
issuer: options["client_email"],
|
514
|
-
signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) }
|
519
|
+
options
|
515
520
|
end
|
516
521
|
|
517
|
-
# rubocop:enable Metrics/AbcSize
|
518
|
-
|
519
522
|
def update_from_signet client
|
520
523
|
@project_id ||= client.project_id if client.respond_to? :project_id
|
521
524
|
@quota_project_id ||= client.quota_project_id if client.respond_to? :quota_project_id
|
@@ -526,7 +529,7 @@ module Google
|
|
526
529
|
hash = stringify_hash_keys hash
|
527
530
|
hash["scope"] ||= options[:scope]
|
528
531
|
hash["target_audience"] ||= options[:target_audience]
|
529
|
-
@project_id ||=
|
532
|
+
@project_id ||= hash["project_id"] || hash["project"]
|
530
533
|
@quota_project_id ||= hash["quota_project_id"]
|
531
534
|
@client = init_client hash, options
|
532
535
|
end
|
@@ -536,7 +539,7 @@ module Google
|
|
536
539
|
json = JSON.parse ::File.read(path)
|
537
540
|
json["scope"] ||= options[:scope]
|
538
541
|
json["target_audience"] ||= options[:target_audience]
|
539
|
-
@project_id ||=
|
542
|
+
@project_id ||= json["project_id"] || json["project"]
|
540
543
|
@quota_project_id ||= json["quota_project_id"]
|
541
544
|
@client = init_client json, options
|
542
545
|
end
|
@@ -42,6 +42,7 @@ module Google
|
|
42
42
|
|
43
43
|
attr_reader :expires_at
|
44
44
|
attr_accessor :access_token
|
45
|
+
attr_accessor :universe_domain
|
45
46
|
|
46
47
|
def expires_within? seconds
|
47
48
|
# This method is needed for BaseClient
|
@@ -85,8 +86,7 @@ module Google
|
|
85
86
|
# true if the credentials represent a workforce pool.
|
86
87
|
# false if they represent a workload.
|
87
88
|
def is_workforce_pool?
|
88
|
-
|
89
|
-
/#{pattern}/.match?(@audience || "")
|
89
|
+
%r{/iam\.googleapis\.com/locations/[^/]+/workforcePools/}.match?(@audience || "")
|
90
90
|
end
|
91
91
|
|
92
92
|
private
|
@@ -111,6 +111,7 @@ module Google
|
|
111
111
|
@quota_project_id = options[:quota_project_id]
|
112
112
|
@project_id = nil
|
113
113
|
@workforce_pool_user_project = options[:workforce_pool_user_project]
|
114
|
+
@universe_domain = options[:universe_domain] || "googleapis.com"
|
114
115
|
|
115
116
|
@expires_at = nil
|
116
117
|
@access_token = nil
|
@@ -73,7 +73,8 @@ module Google
|
|
73
73
|
subject_token_type: user_creds[:subject_token_type],
|
74
74
|
token_url: user_creds[:token_url],
|
75
75
|
credential_source: user_creds[:credential_source],
|
76
|
-
service_account_impersonation_url: user_creds[:service_account_impersonation_url]
|
76
|
+
service_account_impersonation_url: user_creds[:service_account_impersonation_url],
|
77
|
+
universe_domain: user_creds[:universe_domain]
|
77
78
|
)
|
78
79
|
end
|
79
80
|
|
@@ -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.
|
@@ -53,12 +57,13 @@ module Google
|
|
53
57
|
raise ArgumentError, "Cannot specify both scope and target_audience" if scope && target_audience
|
54
58
|
|
55
59
|
if json_key_io
|
56
|
-
private_key, client_email, project_id, quota_project_id = read_json_key json_key_io
|
60
|
+
private_key, client_email, project_id, quota_project_id, universe_domain = read_json_key json_key_io
|
57
61
|
else
|
58
62
|
private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
59
63
|
client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
60
64
|
project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
|
61
65
|
quota_project_id = nil
|
66
|
+
universe_domain = nil
|
62
67
|
end
|
63
68
|
project_id ||= CredentialsLoader.load_gcloud_project_id
|
64
69
|
|
@@ -70,7 +75,8 @@ module Google
|
|
70
75
|
issuer: client_email,
|
71
76
|
signing_key: OpenSSL::PKey::RSA.new(private_key),
|
72
77
|
project_id: project_id,
|
73
|
-
quota_project_id: quota_project_id
|
78
|
+
quota_project_id: quota_project_id,
|
79
|
+
universe_domain: universe_domain || "googleapis.com")
|
74
80
|
.configure_connection(options)
|
75
81
|
end
|
76
82
|
|
@@ -93,16 +99,18 @@ module Google
|
|
93
99
|
# Extends the base class to use a transient
|
94
100
|
# ServiceAccountJwtHeaderCredentials for certain cases.
|
95
101
|
def apply! a_hash, opts = {}
|
96
|
-
|
97
|
-
# obtain an OAuth token, OR if there are scopes but also an assertion
|
98
|
-
# that they are default scopes that shouldn't be used to fetch a token.
|
99
|
-
if target_audience.nil? && (scope.nil? || enable_self_signed_jwt?)
|
102
|
+
if enable_self_signed_jwt?
|
100
103
|
apply_self_signed_jwt! a_hash
|
101
104
|
else
|
102
105
|
super
|
103
106
|
end
|
104
107
|
end
|
105
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
|
+
|
106
114
|
private
|
107
115
|
|
108
116
|
def apply_self_signed_jwt! a_hash
|
@@ -138,6 +146,7 @@ module Google
|
|
138
146
|
extend JsonKeyReader
|
139
147
|
attr_reader :project_id
|
140
148
|
attr_reader :quota_project_id
|
149
|
+
attr_accessor :universe_domain
|
141
150
|
|
142
151
|
# Create a ServiceAccountJwtHeaderCredentials.
|
143
152
|
#
|
@@ -154,14 +163,16 @@ module Google
|
|
154
163
|
def initialize options = {}
|
155
164
|
json_key_io = options[:json_key_io]
|
156
165
|
if json_key_io
|
157
|
-
@private_key, @issuer, @project_id, @quota_project_id =
|
166
|
+
@private_key, @issuer, @project_id, @quota_project_id, @universe_domain =
|
158
167
|
self.class.read_json_key json_key_io
|
159
168
|
else
|
160
169
|
@private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
161
170
|
@issuer = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
162
171
|
@project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
|
163
172
|
@quota_project_id = nil
|
173
|
+
@universe_domain = nil
|
164
174
|
end
|
175
|
+
@universe_domain ||= "googleapis.com"
|
165
176
|
@project_id ||= CredentialsLoader.load_gcloud_project_id
|
166
177
|
@signing_key = OpenSSL::PKey::RSA.new @private_key
|
167
178
|
@scope = options[:scope]
|
@@ -210,6 +221,11 @@ module Google
|
|
210
221
|
|
211
222
|
JWT.encode assertion, @signing_key, SIGNING_ALGORITHM
|
212
223
|
end
|
224
|
+
|
225
|
+
# Duck-types the corresponding method from BaseClient
|
226
|
+
def needs_access_token?
|
227
|
+
false
|
228
|
+
end
|
213
229
|
end
|
214
230
|
end
|
215
231
|
end
|
data/lib/googleauth/signet.rb
CHANGED
@@ -25,6 +25,15 @@ module Signet
|
|
25
25
|
class Client
|
26
26
|
include Google::Auth::BaseClient
|
27
27
|
|
28
|
+
alias update_token_signet_base update_token!
|
29
|
+
|
30
|
+
def update_token! options = {}
|
31
|
+
options = deep_hash_normalize options
|
32
|
+
update_token_signet_base options
|
33
|
+
self.universe_domain = options[:universe_domain] if options.key? :universe_domain
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
28
37
|
def configure_connection options
|
29
38
|
@connection_info =
|
30
39
|
options[:connection_builder] || options[:default_connection]
|
@@ -36,6 +45,9 @@ module Signet
|
|
36
45
|
target_audience ? :id_token : :access_token
|
37
46
|
end
|
38
47
|
|
48
|
+
# Set the universe domain
|
49
|
+
attr_accessor :universe_domain
|
50
|
+
|
39
51
|
alias orig_fetch_access_token! fetch_access_token!
|
40
52
|
def fetch_access_token! options = {}
|
41
53
|
unless options[:connection]
|
@@ -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
|
@@ -50,7 +50,8 @@ module Google
|
|
50
50
|
"client_secret" => ENV[CredentialsLoader::CLIENT_SECRET_VAR],
|
51
51
|
"refresh_token" => ENV[CredentialsLoader::REFRESH_TOKEN_VAR],
|
52
52
|
"project_id" => ENV[CredentialsLoader::PROJECT_ID_VAR],
|
53
|
-
"quota_project_id" => nil
|
53
|
+
"quota_project_id" => nil,
|
54
|
+
"universe_domain" => nil
|
54
55
|
}
|
55
56
|
new(token_credential_uri: TOKEN_CRED_URI,
|
56
57
|
client_id: user_creds["client_id"],
|
@@ -58,7 +59,8 @@ module Google
|
|
58
59
|
refresh_token: user_creds["refresh_token"],
|
59
60
|
project_id: user_creds["project_id"],
|
60
61
|
quota_project_id: user_creds["quota_project_id"],
|
61
|
-
scope: scope
|
62
|
+
scope: scope,
|
63
|
+
universe_domain: user_creds["universe_domain"] || "googleapis.com")
|
62
64
|
.configure_connection(options)
|
63
65
|
end
|
64
66
|
|
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
|
@@ -16,7 +16,7 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0
|
19
|
+
version: '1.0'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: 3.a
|
@@ -26,10 +26,24 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0
|
29
|
+
version: '1.0'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 3.a
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: google-cloud-env
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '2.1'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.1'
|
33
47
|
- !ruby/object:Gem::Dependency
|
34
48
|
name: jwt
|
35
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -165,14 +179,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
165
179
|
requirements:
|
166
180
|
- - ">="
|
167
181
|
- !ruby/object:Gem::Version
|
168
|
-
version: '2.
|
182
|
+
version: '2.7'
|
169
183
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
184
|
requirements:
|
171
185
|
- - ">="
|
172
186
|
- !ruby/object:Gem::Version
|
173
187
|
version: '0'
|
174
188
|
requirements: []
|
175
|
-
rubygems_version: 3.
|
189
|
+
rubygems_version: 3.5.3
|
176
190
|
signing_key:
|
177
191
|
specification_version: 4
|
178
192
|
summary: Google Auth Library for Ruby
|