googleauth 1.12.2 → 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 +29 -0
- data/README.md +10 -1
- data/lib/googleauth/api_key.rb +155 -0
- data/lib/googleauth/bearer_token.rb +148 -0
- data/lib/googleauth/compute_engine.rb +40 -1
- data/lib/googleauth/credentials.rb +107 -25
- data/lib/googleauth/credentials_loader.rb +2 -2
- data/lib/googleauth/default_credentials.rb +13 -2
- data/lib/googleauth/helpers/connection.rb +7 -1
- data/lib/googleauth/impersonated_service_account.rb +282 -0
- data/lib/googleauth/service_account.rb +55 -113
- data/lib/googleauth/service_account_jwt_header.rb +180 -0
- data/lib/googleauth/signet.rb +52 -0
- data/lib/googleauth/user_refresh.rb +43 -0
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth.rb +7 -0
- metadata +10 -9
@@ -16,9 +16,10 @@ require "multi_json"
|
|
16
16
|
require "stringio"
|
17
17
|
|
18
18
|
require "googleauth/credentials_loader"
|
19
|
+
require "googleauth/external_account"
|
19
20
|
require "googleauth/service_account"
|
21
|
+
require "googleauth/service_account_jwt_header"
|
20
22
|
require "googleauth/user_refresh"
|
21
|
-
require "googleauth/external_account"
|
22
23
|
|
23
24
|
module Google
|
24
25
|
# Module Auth provides classes that provide Google-specific authorization
|
@@ -29,8 +30,18 @@ module Google
|
|
29
30
|
class DefaultCredentials
|
30
31
|
extend CredentialsLoader
|
31
32
|
|
32
|
-
|
33
|
+
##
|
34
|
+
# Override CredentialsLoader#make_creds to use the class determined by
|
33
35
|
# loading the json.
|
36
|
+
#
|
37
|
+
# **Important:** If you accept a credential configuration (credential
|
38
|
+
# JSON/File/Stream) from an external source for authentication to Google
|
39
|
+
# Cloud, you must validate it before providing it to any Google API or
|
40
|
+
# library. Providing an unvalidated credential configuration to Google
|
41
|
+
# APIs can compromise the security of your systems and data. For more
|
42
|
+
# information, refer to [Validate credential configurations from external
|
43
|
+
# sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
|
44
|
+
#
|
34
45
|
def self.make_creds options = {}
|
35
46
|
json_key_io = options[:json_key_io]
|
36
47
|
if json_key_io
|
@@ -24,7 +24,13 @@ module Google
|
|
24
24
|
module Connection
|
25
25
|
module_function
|
26
26
|
|
27
|
-
|
27
|
+
def default_connection
|
28
|
+
@default_connection
|
29
|
+
end
|
30
|
+
|
31
|
+
def default_connection= conn
|
32
|
+
@default_connection = conn
|
33
|
+
end
|
28
34
|
|
29
35
|
def connection
|
30
36
|
@default_connection || Faraday.default_connection
|
@@ -0,0 +1,282 @@
|
|
1
|
+
# Copyright 2024 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 "googleauth/signet"
|
16
|
+
require "googleauth/base_client"
|
17
|
+
require "googleauth/helpers/connection"
|
18
|
+
|
19
|
+
module Google
|
20
|
+
module Auth
|
21
|
+
# Authenticates requests using impersonation from base credentials.
|
22
|
+
# This is a two-step process: first authentication claim from the base credentials is created
|
23
|
+
# and then that claim is exchanged for a short-lived token at an IAMCredentials endpoint.
|
24
|
+
# The short-lived token and its expiration time are cached.
|
25
|
+
class ImpersonatedServiceAccountCredentials
|
26
|
+
# @private
|
27
|
+
ERROR_SUFFIX = <<~ERROR.freeze
|
28
|
+
when trying to get security access token
|
29
|
+
from IAM Credentials endpoint using the credentials provided.
|
30
|
+
ERROR
|
31
|
+
|
32
|
+
# @private
|
33
|
+
IAM_SCOPE = ["https://www.googleapis.com/auth/iam".freeze].freeze
|
34
|
+
|
35
|
+
# BaseClient most importantly implements the `:updater_proc` getter,
|
36
|
+
# that returns a reference to an `apply!` method that updates
|
37
|
+
# a hash argument provided with the authorization header containing
|
38
|
+
# the access token (impersonation token in this case).
|
39
|
+
include Google::Auth::BaseClient
|
40
|
+
|
41
|
+
include Helpers::Connection
|
42
|
+
|
43
|
+
# @return [Object] The original authenticated credentials used to fetch short-lived impersonation access tokens
|
44
|
+
attr_reader :base_credentials
|
45
|
+
|
46
|
+
# @return [Object] The modified version of base credentials, tailored for impersonation purposes
|
47
|
+
# with necessary scope adjustments
|
48
|
+
attr_reader :source_credentials
|
49
|
+
|
50
|
+
# @return [String] The URL endpoint used to generate an impersonation token. This URL should follow a specific
|
51
|
+
# format to specify the impersonated service account.
|
52
|
+
attr_reader :impersonation_url
|
53
|
+
|
54
|
+
# @return [Array<String>, String] The scope(s) required for the impersonated access token,
|
55
|
+
# indicating the permissions needed for the short-lived token
|
56
|
+
attr_reader :scope
|
57
|
+
|
58
|
+
# @return [String, nil] The short-lived impersonation access token, retrieved and cached
|
59
|
+
# after making the impersonation request
|
60
|
+
attr_reader :access_token
|
61
|
+
|
62
|
+
# @return [Time, nil] The expiration time of the current access token, used to determine
|
63
|
+
# if the token is still valid
|
64
|
+
attr_reader :expires_at
|
65
|
+
|
66
|
+
# Create a ImpersonatedServiceAccountCredentials
|
67
|
+
# When you use service account impersonation, you start with an authenticated principal
|
68
|
+
# (e.g. your user account or a service account)
|
69
|
+
# and request short-lived credentials for a service account
|
70
|
+
# that has the authorization that your use case requires.
|
71
|
+
#
|
72
|
+
# @param options [Hash] A hash of options to configure the credentials.
|
73
|
+
# @option options [Object] :base_credentials (required) The authenticated principal.
|
74
|
+
# It will be used as following:
|
75
|
+
# * will be duplicated (with IAM scope) to create the source credentials if it supports duplication
|
76
|
+
# * as source credentials otherwise.
|
77
|
+
# @option options [String] :impersonation_url (required) The URL to impersonate the service account.
|
78
|
+
# This URL should follow the format:
|
79
|
+
# `https://iamcredentials.{universe_domain}/v1/projects/-/serviceAccounts/{source_sa_email}:generateAccessToken`,
|
80
|
+
# where:
|
81
|
+
# - `{universe_domain}` is the domain of the IAMCredentials API endpoint (e.g., `googleapis.com`).
|
82
|
+
# - `{source_sa_email}` is the email address of the service account to impersonate.
|
83
|
+
# @option options [Array<String>, String] :scope (required) The scope(s) for the short-lived impersonation token,
|
84
|
+
# defining the permissions required for the token.
|
85
|
+
# @option options [Object] :source_credentials The authenticated principal that will be used
|
86
|
+
# to fetch the short-lived impersonation access token. It is an alternative to providing the base credentials.
|
87
|
+
#
|
88
|
+
# @return [Google::Auth::ImpersonatedServiceAccountCredentials]
|
89
|
+
def self.make_creds options = {}
|
90
|
+
new options
|
91
|
+
end
|
92
|
+
|
93
|
+
# Initializes a new instance of ImpersonatedServiceAccountCredentials.
|
94
|
+
#
|
95
|
+
# @param options [Hash] A hash of options to configure the credentials.
|
96
|
+
# @option options [Object] :base_credentials (required) The authenticated principal.
|
97
|
+
# It will be used as following:
|
98
|
+
# * will be duplicated (with IAM scope) to create the source credentials if it supports duplication
|
99
|
+
# * as source credentials otherwise.
|
100
|
+
# @option options [String] :impersonation_url (required) The URL to impersonate the service account.
|
101
|
+
# This URL should follow the format:
|
102
|
+
# `https://iamcredentials.{universe_domain}/v1/projects/-/serviceAccounts/{source_sa_email}:generateAccessToken`,
|
103
|
+
# where:
|
104
|
+
# - `{universe_domain}` is the domain of the IAMCredentials API endpoint (e.g., `googleapis.com`).
|
105
|
+
# - `{source_sa_email}` is the email address of the service account to impersonate.
|
106
|
+
# @option options [Array<String>, String] :scope (required) The scope(s) for the short-lived impersonation token,
|
107
|
+
# defining the permissions required for the token.
|
108
|
+
# @option options [Object] :source_credentials The authenticated principal that will be used
|
109
|
+
# to fetch the short-lived impersonation access token. It is an alternative to providing the base credentials.
|
110
|
+
# It is redundant to provide both source and base credentials as only source will be used,
|
111
|
+
# but it can be done, e.g. when duplicating existing credentials.
|
112
|
+
#
|
113
|
+
# @raise [ArgumentError] If any of the required options are missing.
|
114
|
+
#
|
115
|
+
# @return [Google::Auth::ImpersonatedServiceAccountCredentials]
|
116
|
+
def initialize options = {}
|
117
|
+
@base_credentials, @impersonation_url, @scope =
|
118
|
+
options.values_at :base_credentials,
|
119
|
+
:impersonation_url,
|
120
|
+
:scope
|
121
|
+
|
122
|
+
# Fail-fast checks for required parameters
|
123
|
+
if @base_credentials.nil? && !options.key?(:source_credentials)
|
124
|
+
raise ArgumentError, "Missing required option: either :base_credentials or :source_credentials"
|
125
|
+
end
|
126
|
+
raise ArgumentError, "Missing required option: :impersonation_url" if @impersonation_url.nil?
|
127
|
+
raise ArgumentError, "Missing required option: :scope" if @scope.nil?
|
128
|
+
|
129
|
+
# Some credentials (all Signet-based ones and this one) include scope and a bunch of transient state
|
130
|
+
# (e.g. refresh status) as part of themselves
|
131
|
+
# so a copy needs to be created with the scope overriden and transient state dropped.
|
132
|
+
#
|
133
|
+
# If a credentials does not support `duplicate` we'll try to use it as is assuming it has a broad enough scope.
|
134
|
+
# This might result in an "access denied" error downstream when the token from that credentials is being used
|
135
|
+
# for the token exchange.
|
136
|
+
@source_credentials = if options.key? :source_credentials
|
137
|
+
options[:source_credentials]
|
138
|
+
elsif @base_credentials.respond_to? :duplicate
|
139
|
+
@base_credentials.duplicate({
|
140
|
+
scope: IAM_SCOPE
|
141
|
+
})
|
142
|
+
else
|
143
|
+
@base_credentials
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Determines whether the current access token expires within the specified number of seconds.
|
148
|
+
#
|
149
|
+
# @param seconds [Integer] The number of seconds to check against the token's expiration time.
|
150
|
+
#
|
151
|
+
# @return [Boolean] Whether the access token expires within the given time frame
|
152
|
+
def expires_within? seconds
|
153
|
+
# This method is needed for BaseClient
|
154
|
+
@expires_at && @expires_at - Time.now.utc < seconds
|
155
|
+
end
|
156
|
+
|
157
|
+
# The universe domain of the impersonated credentials.
|
158
|
+
# Effectively this retrieves the universe domain of the source credentials.
|
159
|
+
#
|
160
|
+
# @return [String] The universe domain of the credentials.
|
161
|
+
def universe_domain
|
162
|
+
@source_credentials.universe_domain
|
163
|
+
end
|
164
|
+
|
165
|
+
# @return [Logger, nil] The logger of the credentials.
|
166
|
+
def logger
|
167
|
+
@source_credentials.logger if source_credentials.respond_to? :logger
|
168
|
+
end
|
169
|
+
|
170
|
+
# Creates a duplicate of these credentials without transient token state
|
171
|
+
#
|
172
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
173
|
+
# The following keys are recognized
|
174
|
+
# * `base_credentials` the base credentials used to initialize the impersonation
|
175
|
+
# * `source_credentials` the authenticated credentials which usually would be
|
176
|
+
# base credentials with scope overridden to IAM_SCOPE
|
177
|
+
# * `impersonation_url` the URL to use to make an impersonation token exchange
|
178
|
+
# * `scope` the scope(s) to access
|
179
|
+
#
|
180
|
+
# @return [Google::Auth::ImpersonatedServiceAccountCredentials]
|
181
|
+
def duplicate options = {}
|
182
|
+
options = deep_hash_normalize options
|
183
|
+
|
184
|
+
options = {
|
185
|
+
base_credentials: @base_credentials,
|
186
|
+
source_credentials: @source_credentials,
|
187
|
+
impersonation_url: @impersonation_url,
|
188
|
+
scope: @scope
|
189
|
+
}.merge(options)
|
190
|
+
|
191
|
+
self.class.new options
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
# Generates a new impersonation access token by exchanging the source credentials' token
|
197
|
+
# at the impersonation URL.
|
198
|
+
#
|
199
|
+
# This method first fetches an access token from the source credentials and then exchanges it
|
200
|
+
# for an impersonation token using the specified impersonation URL. The generated token and
|
201
|
+
# its expiration time are cached for subsequent use.
|
202
|
+
#
|
203
|
+
# @param _options [Hash] (optional) Additional options for token retrieval (currently unused).
|
204
|
+
#
|
205
|
+
# @raise [Signet::UnexpectedStatusError] If the response status is 403 or 500.
|
206
|
+
# @raise [Signet::AuthorizationError] For other unexpected response statuses.
|
207
|
+
#
|
208
|
+
# @return [String] The newly generated impersonation access token.
|
209
|
+
def fetch_access_token! _options = {}
|
210
|
+
auth_header = {}
|
211
|
+
auth_header = @source_credentials.updater_proc.call auth_header
|
212
|
+
|
213
|
+
resp = connection.post @impersonation_url do |req|
|
214
|
+
req.headers.merge! auth_header
|
215
|
+
req.headers["Content-Type"] = "application/json"
|
216
|
+
req.body = MultiJson.dump({ scope: @scope })
|
217
|
+
end
|
218
|
+
|
219
|
+
case resp.status
|
220
|
+
when 200
|
221
|
+
response = MultiJson.load resp.body
|
222
|
+
self.expires_at = response["expireTime"]
|
223
|
+
@access_token = response["accessToken"]
|
224
|
+
access_token
|
225
|
+
when 403, 500
|
226
|
+
msg = "Unexpected error code #{resp.status}.\n #{resp.env.response_body} #{ERROR_SUFFIX}"
|
227
|
+
raise Signet::UnexpectedStatusError, msg
|
228
|
+
else
|
229
|
+
msg = "Unexpected error code #{resp.status}.\n #{resp.env.response_body} #{ERROR_SUFFIX}"
|
230
|
+
raise Signet::AuthorizationError, msg
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Setter for the expires_at value that makes sure it is converted
|
235
|
+
# to Time object.
|
236
|
+
def expires_at= new_expires_at
|
237
|
+
@expires_at = normalize_timestamp new_expires_at
|
238
|
+
end
|
239
|
+
|
240
|
+
# Returns the type of token (access_token).
|
241
|
+
# This method is needed for BaseClient.
|
242
|
+
def token_type
|
243
|
+
:access_token
|
244
|
+
end
|
245
|
+
|
246
|
+
# Normalizes a timestamp to a Time object.
|
247
|
+
#
|
248
|
+
# @param time [Time, String, nil] The timestamp to normalize.
|
249
|
+
#
|
250
|
+
# @return [Time, nil] The normalized Time object, or nil if the input is nil.
|
251
|
+
#
|
252
|
+
# @raise [RuntimeError] If the input is not a Time, String, or nil.
|
253
|
+
def normalize_timestamp time
|
254
|
+
case time
|
255
|
+
when NilClass
|
256
|
+
nil
|
257
|
+
when Time
|
258
|
+
time
|
259
|
+
when String
|
260
|
+
Time.parse time
|
261
|
+
else
|
262
|
+
raise "Invalid time value #{time}"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Convert all keys in this hash (nested) to symbols for uniform retrieval
|
267
|
+
def recursive_hash_normalize_keys val
|
268
|
+
if val.is_a? Hash
|
269
|
+
deep_hash_normalize val
|
270
|
+
else
|
271
|
+
val
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def deep_hash_normalize old_hash
|
276
|
+
sym_hash = {}
|
277
|
+
old_hash&.each { |k, v| sym_hash[k.to_sym] = recursive_hash_normalize_keys v }
|
278
|
+
sym_hash
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
@@ -12,13 +12,15 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
require "jwt"
|
16
|
+
require "multi_json"
|
17
|
+
require "stringio"
|
18
|
+
|
15
19
|
require "google/logging/message"
|
16
20
|
require "googleauth/signet"
|
17
21
|
require "googleauth/credentials_loader"
|
18
22
|
require "googleauth/json_key_reader"
|
19
|
-
require "
|
20
|
-
require "multi_json"
|
21
|
-
require "stringio"
|
23
|
+
require "googleauth/service_account_jwt_header"
|
22
24
|
|
23
25
|
module Google
|
24
26
|
# Module Auth provides classes that provide Google-specific authorization
|
@@ -81,6 +83,30 @@ module Google
|
|
81
83
|
.configure_connection(options)
|
82
84
|
end
|
83
85
|
|
86
|
+
# Creates a duplicate of these credentials
|
87
|
+
# without the Signet::OAuth2::Client-specific
|
88
|
+
# transient state (e.g. cached tokens)
|
89
|
+
#
|
90
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
91
|
+
# The following keys are recognized in addition to keys in the
|
92
|
+
# Signet::OAuth2::Client
|
93
|
+
# * `:enable_self_signed_jwt` Whether the self-signed JWT should
|
94
|
+
# be used for the authentication
|
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
|
+
enable_self_signed_jwt: @enable_self_signed_jwt,
|
103
|
+
project_id: project_id,
|
104
|
+
quota_project_id: quota_project_id,
|
105
|
+
logger: logger
|
106
|
+
}.merge(options)
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
84
110
|
# Handles certain escape sequences that sometimes appear in input.
|
85
111
|
# Specifically, interprets the "\n" sequence for newline, and removes
|
86
112
|
# enclosing quotes.
|
@@ -112,6 +138,32 @@ module Google
|
|
112
138
|
super && !enable_self_signed_jwt?
|
113
139
|
end
|
114
140
|
|
141
|
+
# Destructively updates these credentials
|
142
|
+
#
|
143
|
+
# This method is called by `Signet::OAuth2::Client`'s constructor
|
144
|
+
#
|
145
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
146
|
+
# The following keys are recognized in addition to keys in the
|
147
|
+
# Signet::OAuth2::Client
|
148
|
+
# * `:enable_self_signed_jwt` Whether the self-signed JWT should
|
149
|
+
# be used for the authentication
|
150
|
+
# * `project_id` the project id to use during the authentication
|
151
|
+
# * `quota_project_id` the quota project id to use
|
152
|
+
# during the authentication
|
153
|
+
# @return [Google::Auth::ServiceAccountCredentials]
|
154
|
+
def update! options = {}
|
155
|
+
# Normalize all keys to symbols to allow indifferent access.
|
156
|
+
options = deep_hash_normalize options
|
157
|
+
|
158
|
+
@enable_self_signed_jwt = options[:enable_self_signed_jwt] ? true : false
|
159
|
+
@project_id = options[:project_id] if options.key? :project_id
|
160
|
+
@quota_project_id = options[:quota_project_id] if options.key? :quota_project_id
|
161
|
+
|
162
|
+
super(options)
|
163
|
+
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
115
167
|
private
|
116
168
|
|
117
169
|
def apply_self_signed_jwt! a_hash
|
@@ -128,115 +180,5 @@ module Google
|
|
128
180
|
alt.apply! a_hash
|
129
181
|
end
|
130
182
|
end
|
131
|
-
|
132
|
-
# Authenticates requests using Google's Service Account credentials via
|
133
|
-
# JWT Header.
|
134
|
-
#
|
135
|
-
# This class allows authorizing requests for service accounts directly
|
136
|
-
# from credentials from a json key file downloaded from the developer
|
137
|
-
# console (via 'Generate new Json Key'). It is not part of any OAuth2
|
138
|
-
# flow, rather it creates a JWT and sends that as a credential.
|
139
|
-
#
|
140
|
-
# cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
|
141
|
-
class ServiceAccountJwtHeaderCredentials
|
142
|
-
JWT_AUD_URI_KEY = :jwt_aud_uri
|
143
|
-
AUTH_METADATA_KEY = Google::Auth::BaseClient::AUTH_METADATA_KEY
|
144
|
-
TOKEN_CRED_URI = "https://www.googleapis.com/oauth2/v4/token".freeze
|
145
|
-
SIGNING_ALGORITHM = "RS256".freeze
|
146
|
-
EXPIRY = 60
|
147
|
-
extend CredentialsLoader
|
148
|
-
extend JsonKeyReader
|
149
|
-
attr_reader :project_id
|
150
|
-
attr_reader :quota_project_id
|
151
|
-
attr_accessor :universe_domain
|
152
|
-
attr_accessor :logger
|
153
|
-
|
154
|
-
# Create a ServiceAccountJwtHeaderCredentials.
|
155
|
-
#
|
156
|
-
# @param json_key_io [IO] an IO from which the JSON key can be read
|
157
|
-
# @param scope [string|array|nil] the scope(s) to access
|
158
|
-
def self.make_creds options = {}
|
159
|
-
json_key_io, scope = options.values_at :json_key_io, :scope
|
160
|
-
new json_key_io: json_key_io, scope: scope
|
161
|
-
end
|
162
|
-
|
163
|
-
# Initializes a ServiceAccountJwtHeaderCredentials.
|
164
|
-
#
|
165
|
-
# @param json_key_io [IO] an IO from which the JSON key can be read
|
166
|
-
def initialize options = {}
|
167
|
-
json_key_io = options[:json_key_io]
|
168
|
-
if json_key_io
|
169
|
-
@private_key, @issuer, @project_id, @quota_project_id, @universe_domain =
|
170
|
-
self.class.read_json_key json_key_io
|
171
|
-
else
|
172
|
-
@private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
173
|
-
@issuer = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
174
|
-
@project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
|
175
|
-
@quota_project_id = nil
|
176
|
-
@universe_domain = nil
|
177
|
-
end
|
178
|
-
@universe_domain ||= "googleapis.com"
|
179
|
-
@project_id ||= CredentialsLoader.load_gcloud_project_id
|
180
|
-
@signing_key = OpenSSL::PKey::RSA.new @private_key
|
181
|
-
@scope = options[:scope]
|
182
|
-
end
|
183
|
-
|
184
|
-
# Construct a jwt token if the JWT_AUD_URI key is present in the input
|
185
|
-
# hash.
|
186
|
-
#
|
187
|
-
# The jwt token is used as the value of a 'Bearer '.
|
188
|
-
def apply! a_hash, opts = {}
|
189
|
-
jwt_aud_uri = a_hash.delete JWT_AUD_URI_KEY
|
190
|
-
return a_hash if jwt_aud_uri.nil? && @scope.nil?
|
191
|
-
jwt_token = new_jwt_token jwt_aud_uri, opts
|
192
|
-
a_hash[AUTH_METADATA_KEY] = "Bearer #{jwt_token}"
|
193
|
-
logger&.debug do
|
194
|
-
hash = Digest::SHA256.hexdigest jwt_token
|
195
|
-
Google::Logging::Message.from message: "Sending JWT auth token. (sha256:#{hash})"
|
196
|
-
end
|
197
|
-
a_hash
|
198
|
-
end
|
199
|
-
|
200
|
-
# Returns a clone of a_hash updated with the authorization header
|
201
|
-
def apply a_hash, opts = {}
|
202
|
-
a_copy = a_hash.clone
|
203
|
-
apply! a_copy, opts
|
204
|
-
a_copy
|
205
|
-
end
|
206
|
-
|
207
|
-
# Returns a reference to the #apply method, suitable for passing as
|
208
|
-
# a closure
|
209
|
-
def updater_proc
|
210
|
-
proc { |a_hash, opts = {}| apply a_hash, opts }
|
211
|
-
end
|
212
|
-
|
213
|
-
# Creates a jwt uri token.
|
214
|
-
def new_jwt_token jwt_aud_uri = nil, options = {}
|
215
|
-
now = Time.new
|
216
|
-
skew = options[:skew] || 60
|
217
|
-
assertion = {
|
218
|
-
"iss" => @issuer,
|
219
|
-
"sub" => @issuer,
|
220
|
-
"exp" => (now + EXPIRY).to_i,
|
221
|
-
"iat" => (now - skew).to_i
|
222
|
-
}
|
223
|
-
|
224
|
-
jwt_aud_uri = nil if @scope
|
225
|
-
|
226
|
-
assertion["scope"] = Array(@scope).join " " if @scope
|
227
|
-
assertion["aud"] = jwt_aud_uri if jwt_aud_uri
|
228
|
-
|
229
|
-
logger&.debug do
|
230
|
-
Google::Logging::Message.from message: "JWT assertion: #{assertion}"
|
231
|
-
end
|
232
|
-
|
233
|
-
JWT.encode assertion, @signing_key, SIGNING_ALGORITHM
|
234
|
-
end
|
235
|
-
|
236
|
-
# Duck-types the corresponding method from BaseClient
|
237
|
-
def needs_access_token?
|
238
|
-
false
|
239
|
-
end
|
240
|
-
end
|
241
183
|
end
|
242
184
|
end
|