googleauth 1.8.1 → 1.14.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 +96 -0
- data/README.md +49 -1
- data/lib/googleauth/api_key.rb +155 -0
- data/lib/googleauth/application_default.rb +1 -5
- data/lib/googleauth/base_client.rb +16 -4
- data/lib/googleauth/bearer_token.rb +148 -0
- data/lib/googleauth/compute_engine.rb +180 -44
- data/lib/googleauth/credentials.rb +169 -56
- data/lib/googleauth/credentials_loader.rb +2 -2
- data/lib/googleauth/default_credentials.rb +13 -2
- data/lib/googleauth/external_account/base_credentials.rb +37 -5
- data/lib/googleauth/external_account.rb +2 -1
- data/lib/googleauth/helpers/connection.rb +7 -1
- data/lib/googleauth/id_tokens.rb +0 -2
- data/lib/googleauth/impersonated_service_account.rb +282 -0
- data/lib/googleauth/json_key_reader.rb +2 -1
- data/lib/googleauth/service_account.rb +72 -103
- data/lib/googleauth/service_account_jwt_header.rb +180 -0
- data/lib/googleauth/signet.rb +149 -2
- data/lib/googleauth/token_store.rb +3 -3
- data/lib/googleauth/user_authorizer.rb +54 -4
- data/lib/googleauth/user_refresh.rb +47 -2
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +15 -4
- data/lib/googleauth.rb +7 -0
- metadata +40 -11
@@ -0,0 +1,180 @@
|
|
1
|
+
# Copyright 2025 Google, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "google/logging/message"
|
16
|
+
require "googleauth/credentials_loader"
|
17
|
+
require "googleauth/json_key_reader"
|
18
|
+
require "jwt"
|
19
|
+
|
20
|
+
module Google
|
21
|
+
# Module Auth provides classes that provide Google-specific authorization
|
22
|
+
# used to access Google APIs.
|
23
|
+
module Auth
|
24
|
+
# Authenticates requests using Google's Service Account credentials via
|
25
|
+
# JWT Header.
|
26
|
+
#
|
27
|
+
# This class allows authorizing requests for service accounts directly
|
28
|
+
# from credentials from a json key file downloaded from the developer
|
29
|
+
# console (via 'Generate new Json Key'). It is not part of any OAuth2
|
30
|
+
# flow, rather it creates a JWT and sends that as a credential.
|
31
|
+
#
|
32
|
+
# cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
|
33
|
+
class ServiceAccountJwtHeaderCredentials
|
34
|
+
JWT_AUD_URI_KEY = :jwt_aud_uri
|
35
|
+
AUTH_METADATA_KEY = Google::Auth::BaseClient::AUTH_METADATA_KEY
|
36
|
+
TOKEN_CRED_URI = "https://www.googleapis.com/oauth2/v4/token".freeze
|
37
|
+
SIGNING_ALGORITHM = "RS256".freeze
|
38
|
+
EXPIRY = 60
|
39
|
+
|
40
|
+
extend CredentialsLoader
|
41
|
+
extend JsonKeyReader
|
42
|
+
|
43
|
+
attr_reader :project_id
|
44
|
+
attr_reader :quota_project_id
|
45
|
+
attr_accessor :universe_domain
|
46
|
+
attr_accessor :logger
|
47
|
+
|
48
|
+
# Create a ServiceAccountJwtHeaderCredentials.
|
49
|
+
#
|
50
|
+
# @param json_key_io [IO] an IO from which the JSON key can be read
|
51
|
+
# @param scope [string|array|nil] the scope(s) to access
|
52
|
+
def self.make_creds options = {}
|
53
|
+
json_key_io, scope = options.values_at :json_key_io, :scope
|
54
|
+
new json_key_io: json_key_io, scope: scope
|
55
|
+
end
|
56
|
+
|
57
|
+
# Initializes a ServiceAccountJwtHeaderCredentials.
|
58
|
+
#
|
59
|
+
# @param json_key_io [IO] an IO from which the JSON key can be read
|
60
|
+
def initialize options = {}
|
61
|
+
json_key_io = options[:json_key_io]
|
62
|
+
if json_key_io
|
63
|
+
@private_key, @issuer, @project_id, @quota_project_id, @universe_domain =
|
64
|
+
self.class.read_json_key json_key_io
|
65
|
+
else
|
66
|
+
@private_key = options.key?(:private_key) ? options[:private_key] : ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
67
|
+
@issuer = options.key?(:issuer) ? options[:issuer] : ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
68
|
+
@project_id = options.key?(:project_id) ? options[:project_id] : ENV[CredentialsLoader::PROJECT_ID_VAR]
|
69
|
+
@quota_project_id = options[:quota_project_id] if options.key? :quota_project_id
|
70
|
+
@universe_domain = options[:universe_domain] if options.key? :universe_domain
|
71
|
+
end
|
72
|
+
@universe_domain ||= "googleapis.com"
|
73
|
+
@project_id ||= CredentialsLoader.load_gcloud_project_id
|
74
|
+
@signing_key = OpenSSL::PKey::RSA.new @private_key
|
75
|
+
@scope = options[:scope] if options.key? :scope
|
76
|
+
@logger = options[:logger] if options.key? :logger
|
77
|
+
end
|
78
|
+
|
79
|
+
# Creates a duplicate of these credentials
|
80
|
+
#
|
81
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
82
|
+
# The following keys are recognized
|
83
|
+
# * `private key` the private key in string form
|
84
|
+
# * `issuer` the SA issuer
|
85
|
+
# * `scope` the scope(s) to access
|
86
|
+
# * `project_id` the project id to use during the authentication
|
87
|
+
# * `quota_project_id` the quota project id to use
|
88
|
+
# * `universe_domain` the universe domain of the credentials
|
89
|
+
def duplicate options = {}
|
90
|
+
options = deep_hash_normalize options
|
91
|
+
|
92
|
+
options = {
|
93
|
+
private_key: @private_key,
|
94
|
+
issuer: @issuer,
|
95
|
+
scope: @scope,
|
96
|
+
project_id: project_id,
|
97
|
+
quota_project_id: quota_project_id,
|
98
|
+
universe_domain: universe_domain,
|
99
|
+
logger: logger
|
100
|
+
}.merge(options)
|
101
|
+
|
102
|
+
self.class.new options
|
103
|
+
end
|
104
|
+
|
105
|
+
# Construct a jwt token if the JWT_AUD_URI key is present in the input
|
106
|
+
# hash.
|
107
|
+
#
|
108
|
+
# The jwt token is used as the value of a 'Bearer '.
|
109
|
+
def apply! a_hash, opts = {}
|
110
|
+
jwt_aud_uri = a_hash.delete JWT_AUD_URI_KEY
|
111
|
+
return a_hash if jwt_aud_uri.nil? && @scope.nil?
|
112
|
+
jwt_token = new_jwt_token jwt_aud_uri, opts
|
113
|
+
a_hash[AUTH_METADATA_KEY] = "Bearer #{jwt_token}"
|
114
|
+
logger&.debug do
|
115
|
+
hash = Digest::SHA256.hexdigest jwt_token
|
116
|
+
Google::Logging::Message.from message: "Sending JWT auth token. (sha256:#{hash})"
|
117
|
+
end
|
118
|
+
a_hash
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns a clone of a_hash updated with the authorization header
|
122
|
+
def apply a_hash, opts = {}
|
123
|
+
a_copy = a_hash.clone
|
124
|
+
apply! a_copy, opts
|
125
|
+
a_copy
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns a reference to the #apply method, suitable for passing as
|
129
|
+
# a closure
|
130
|
+
def updater_proc
|
131
|
+
proc { |a_hash, opts = {}| apply a_hash, opts }
|
132
|
+
end
|
133
|
+
|
134
|
+
# Creates a jwt uri token.
|
135
|
+
def new_jwt_token jwt_aud_uri = nil, options = {}
|
136
|
+
now = Time.new
|
137
|
+
skew = options[:skew] || 60
|
138
|
+
assertion = {
|
139
|
+
"iss" => @issuer,
|
140
|
+
"sub" => @issuer,
|
141
|
+
"exp" => (now + EXPIRY).to_i,
|
142
|
+
"iat" => (now - skew).to_i
|
143
|
+
}
|
144
|
+
|
145
|
+
jwt_aud_uri = nil if @scope
|
146
|
+
|
147
|
+
assertion["scope"] = Array(@scope).join " " if @scope
|
148
|
+
assertion["aud"] = jwt_aud_uri if jwt_aud_uri
|
149
|
+
|
150
|
+
logger&.debug do
|
151
|
+
Google::Logging::Message.from message: "JWT assertion: #{assertion}"
|
152
|
+
end
|
153
|
+
|
154
|
+
JWT.encode assertion, @signing_key, SIGNING_ALGORITHM
|
155
|
+
end
|
156
|
+
|
157
|
+
# Duck-types the corresponding method from BaseClient
|
158
|
+
def needs_access_token?
|
159
|
+
false
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def deep_hash_normalize old_hash
|
165
|
+
sym_hash = {}
|
166
|
+
old_hash&.each { |k, v| sym_hash[k.to_sym] = recursive_hash_normalize_keys v }
|
167
|
+
sym_hash
|
168
|
+
end
|
169
|
+
|
170
|
+
# Convert all keys in this hash (nested) to symbols for uniform retrieval
|
171
|
+
def recursive_hash_normalize_keys val
|
172
|
+
if val.is_a? Hash
|
173
|
+
deep_hash_normalize val
|
174
|
+
else
|
175
|
+
val
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
data/lib/googleauth/signet.rb
CHANGED
@@ -12,6 +12,8 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
require "base64"
|
16
|
+
require "json"
|
15
17
|
require "signet/oauth_2/client"
|
16
18
|
require "googleauth/base_client"
|
17
19
|
|
@@ -25,6 +27,33 @@ module Signet
|
|
25
27
|
class Client
|
26
28
|
include Google::Auth::BaseClient
|
27
29
|
|
30
|
+
alias update_token_signet_base update_token!
|
31
|
+
|
32
|
+
def update_token! options = {}
|
33
|
+
options = deep_hash_normalize options
|
34
|
+
id_token_expires_at = expires_at_from_id_token options[:id_token]
|
35
|
+
options[:expires_at] = id_token_expires_at if id_token_expires_at
|
36
|
+
update_token_signet_base options
|
37
|
+
self.universe_domain = options[:universe_domain] if options.key? :universe_domain
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
alias update_signet_base update!
|
42
|
+
def update! options = {}
|
43
|
+
# Normalize all keys to symbols to allow indifferent access.
|
44
|
+
options = deep_hash_normalize options
|
45
|
+
|
46
|
+
# This `update!` method "overide" adds the `@logger`` update and
|
47
|
+
# the `universe_domain` update.
|
48
|
+
#
|
49
|
+
# The `universe_domain` is also updated in `update_token!` but is
|
50
|
+
# included here for completeness
|
51
|
+
self.universe_domain = options[:universe_domain] if options.key? :universe_domain
|
52
|
+
@logger = options[:logger] if options.key? :logger
|
53
|
+
|
54
|
+
update_signet_base options
|
55
|
+
end
|
56
|
+
|
28
57
|
def configure_connection options
|
29
58
|
@connection_info =
|
30
59
|
options[:connection_builder] || options[:default_connection]
|
@@ -36,6 +65,9 @@ module Signet
|
|
36
65
|
target_audience ? :id_token : :access_token
|
37
66
|
end
|
38
67
|
|
68
|
+
# Set the universe domain
|
69
|
+
attr_accessor :universe_domain
|
70
|
+
|
39
71
|
alias orig_fetch_access_token! fetch_access_token!
|
40
72
|
def fetch_access_token! options = {}
|
41
73
|
unless options[:connection]
|
@@ -49,6 +81,24 @@ module Signet
|
|
49
81
|
info
|
50
82
|
end
|
51
83
|
|
84
|
+
alias googleauth_orig_generate_access_token_request generate_access_token_request
|
85
|
+
def generate_access_token_request options = {}
|
86
|
+
parameters = googleauth_orig_generate_access_token_request options
|
87
|
+
logger&.info do
|
88
|
+
Google::Logging::Message.from(
|
89
|
+
message: "Requesting access token from #{parameters['grant_type']}",
|
90
|
+
"credentialsId" => object_id
|
91
|
+
)
|
92
|
+
end
|
93
|
+
logger&.debug do
|
94
|
+
Google::Logging::Message.from(
|
95
|
+
message: "Token fetch params: #{parameters}",
|
96
|
+
"credentialsId" => object_id
|
97
|
+
)
|
98
|
+
end
|
99
|
+
parameters
|
100
|
+
end
|
101
|
+
|
52
102
|
def build_default_connection
|
53
103
|
if !defined?(@connection_info)
|
54
104
|
nil
|
@@ -63,20 +113,117 @@ module Signet
|
|
63
113
|
retry_count = 0
|
64
114
|
|
65
115
|
begin
|
66
|
-
yield
|
116
|
+
yield.tap { |resp| log_response resp }
|
67
117
|
rescue StandardError => e
|
68
|
-
|
118
|
+
if e.is_a?(Signet::AuthorizationError) || e.is_a?(Signet::ParseError)
|
119
|
+
log_auth_error e
|
120
|
+
raise e
|
121
|
+
end
|
69
122
|
|
70
123
|
if retry_count < max_retry_count
|
124
|
+
log_transient_error e
|
71
125
|
retry_count += 1
|
72
126
|
sleep retry_count * 0.3
|
73
127
|
retry
|
74
128
|
else
|
129
|
+
log_retries_exhausted e
|
75
130
|
msg = "Unexpected error: #{e.inspect}"
|
76
131
|
raise Signet::AuthorizationError, msg
|
77
132
|
end
|
78
133
|
end
|
79
134
|
end
|
135
|
+
|
136
|
+
# Creates a duplicate of these credentials
|
137
|
+
# without the Signet::OAuth2::Client-specific
|
138
|
+
# transient state (e.g. cached tokens)
|
139
|
+
#
|
140
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
141
|
+
# @see Signet::OAuth2::Client#update!
|
142
|
+
def duplicate options = {}
|
143
|
+
options = deep_hash_normalize options
|
144
|
+
|
145
|
+
opts = {
|
146
|
+
authorization_uri: @authorization_uri,
|
147
|
+
token_credential_uri: @token_credential_uri,
|
148
|
+
client_id: @client_id,
|
149
|
+
client_secret: @client_secret,
|
150
|
+
scope: @scope,
|
151
|
+
target_audience: @target_audience,
|
152
|
+
redirect_uri: @redirect_uri,
|
153
|
+
username: @username,
|
154
|
+
password: @password,
|
155
|
+
issuer: @issuer,
|
156
|
+
person: @person,
|
157
|
+
sub: @sub,
|
158
|
+
audience: @audience,
|
159
|
+
signing_key: @signing_key,
|
160
|
+
extension_parameters: @extension_parameters,
|
161
|
+
additional_parameters: @additional_parameters,
|
162
|
+
access_type: @access_type,
|
163
|
+
universe_domain: @universe_domain,
|
164
|
+
logger: @logger
|
165
|
+
}.merge(options)
|
166
|
+
|
167
|
+
new_client = self.class.new opts
|
168
|
+
|
169
|
+
new_client.configure_connection options
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def expires_at_from_id_token id_token
|
175
|
+
match = /^[\w=-]+\.([\w=-]+)\.[\w=-]+$/.match id_token.to_s
|
176
|
+
return unless match
|
177
|
+
json = JSON.parse Base64.urlsafe_decode64 match[1]
|
178
|
+
return unless json.key? "exp"
|
179
|
+
Time.at json["exp"].to_i
|
180
|
+
rescue StandardError
|
181
|
+
# Shouldn't happen unless we get a garbled ID token
|
182
|
+
nil
|
183
|
+
end
|
184
|
+
|
185
|
+
def log_response token_response
|
186
|
+
response_hash = JSON.parse token_response rescue {}
|
187
|
+
if response_hash["access_token"]
|
188
|
+
digest = Digest::SHA256.hexdigest response_hash["access_token"]
|
189
|
+
response_hash["access_token"] = "(sha256:#{digest})"
|
190
|
+
end
|
191
|
+
if response_hash["id_token"]
|
192
|
+
digest = Digest::SHA256.hexdigest response_hash["id_token"]
|
193
|
+
response_hash["id_token"] = "(sha256:#{digest})"
|
194
|
+
end
|
195
|
+
Google::Logging::Message.from(
|
196
|
+
message: "Received auth token response: #{response_hash}",
|
197
|
+
"credentialsId" => object_id
|
198
|
+
)
|
199
|
+
end
|
200
|
+
|
201
|
+
def log_auth_error err
|
202
|
+
logger&.info do
|
203
|
+
Google::Logging::Message.from(
|
204
|
+
message: "Auth error when fetching auth token: #{err}",
|
205
|
+
"credentialsId" => object_id
|
206
|
+
)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def log_transient_error err
|
211
|
+
logger&.info do
|
212
|
+
Google::Logging::Message.from(
|
213
|
+
message: "Transient error when fetching auth token: #{err}",
|
214
|
+
"credentialsId" => object_id
|
215
|
+
)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def log_retries_exhausted err
|
220
|
+
logger&.info do
|
221
|
+
Google::Logging::Message.from(
|
222
|
+
message: "Exhausted retries when fetching auth token: #{err}",
|
223
|
+
"credentialsId" => object_id
|
224
|
+
)
|
225
|
+
end
|
226
|
+
end
|
80
227
|
end
|
81
228
|
end
|
82
229
|
end
|
@@ -29,7 +29,7 @@ module Google
|
|
29
29
|
# @return [String]
|
30
30
|
# The loaded token data.
|
31
31
|
def load _id
|
32
|
-
raise "
|
32
|
+
raise NoMethodError, "load not implemented"
|
33
33
|
end
|
34
34
|
|
35
35
|
# Put the token data into storage for the given ID.
|
@@ -39,7 +39,7 @@ module Google
|
|
39
39
|
# @param [String] token
|
40
40
|
# The token data to store.
|
41
41
|
def store _id, _token
|
42
|
-
raise "
|
42
|
+
raise NoMethodError, "store not implemented"
|
43
43
|
end
|
44
44
|
|
45
45
|
# Remove the token data from storage for the given ID.
|
@@ -47,7 +47,7 @@ module Google
|
|
47
47
|
# @param [String] id
|
48
48
|
# ID of the token data to delete
|
49
49
|
def delete _id
|
50
|
-
raise "
|
50
|
+
raise NoMethodError, "delete not implemented"
|
51
51
|
end
|
52
52
|
end
|
53
53
|
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
|
@@ -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
|
|
@@ -83,6 +85,26 @@ module Google
|
|
83
85
|
super options
|
84
86
|
end
|
85
87
|
|
88
|
+
# Creates a duplicate of these credentials
|
89
|
+
# without the Signet::OAuth2::Client-specific
|
90
|
+
# transient state (e.g. cached tokens)
|
91
|
+
#
|
92
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
93
|
+
# The following keys are recognized in addition to keys in the
|
94
|
+
# Signet::OAuth2::Client
|
95
|
+
# * `project_id` the project id to use during the authentication
|
96
|
+
# * `quota_project_id` the quota project id to use
|
97
|
+
# during the authentication
|
98
|
+
def duplicate options = {}
|
99
|
+
options = deep_hash_normalize options
|
100
|
+
super(
|
101
|
+
{
|
102
|
+
project_id: @project_id,
|
103
|
+
quota_project_id: @quota_project_id
|
104
|
+
}.merge(options)
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
86
108
|
# Revokes the credential
|
87
109
|
def revoke! options = {}
|
88
110
|
c = options[:connection] || Faraday.default_connection
|
@@ -112,6 +134,29 @@ module Google
|
|
112
134
|
Google::Auth::ScopeUtil.normalize(scope)
|
113
135
|
missing_scope.empty?
|
114
136
|
end
|
137
|
+
|
138
|
+
# Destructively updates these credentials
|
139
|
+
#
|
140
|
+
# This method is called by `Signet::OAuth2::Client`'s constructor
|
141
|
+
#
|
142
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
143
|
+
# The following keys are recognized in addition to keys in the
|
144
|
+
# Signet::OAuth2::Client
|
145
|
+
# * `project_id` the project id to use during the authentication
|
146
|
+
# * `quota_project_id` the quota project id to use
|
147
|
+
# during the authentication
|
148
|
+
# @return [Google::Auth::UserRefreshCredentials]
|
149
|
+
def update! options = {}
|
150
|
+
# Normalize all keys to symbols to allow indifferent access.
|
151
|
+
options = deep_hash_normalize options
|
152
|
+
|
153
|
+
@project_id = options[:project_id] if options.key? :project_id
|
154
|
+
@quota_project_id = options[:quota_project_id] if options.key? :quota_project_id
|
155
|
+
|
156
|
+
super(options)
|
157
|
+
|
158
|
+
self
|
159
|
+
end
|
115
160
|
end
|
116
161
|
end
|
117
162
|
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
|
data/lib/googleauth.rb
CHANGED
@@ -13,9 +13,16 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
15
|
require "googleauth/application_default"
|
16
|
+
require "googleauth/api_key"
|
17
|
+
require "googleauth/bearer_token"
|
16
18
|
require "googleauth/client_id"
|
17
19
|
require "googleauth/credentials"
|
18
20
|
require "googleauth/default_credentials"
|
21
|
+
require "googleauth/external_account"
|
19
22
|
require "googleauth/id_tokens"
|
23
|
+
require "googleauth/impersonated_service_account"
|
24
|
+
require "googleauth/service_account"
|
25
|
+
require "googleauth/service_account_jwt_header"
|
20
26
|
require "googleauth/user_authorizer"
|
27
|
+
require "googleauth/user_refresh"
|
21
28
|
require "googleauth/web_user_authorizer"
|