googleauth 1.8.0 → 1.15.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 +117 -0
- data/Credentials.md +106 -0
- data/Errors.md +152 -0
- data/README.md +49 -1
- data/lib/googleauth/api_key.rb +164 -0
- data/lib/googleauth/application_default.rb +6 -8
- data/lib/googleauth/base_client.rb +21 -4
- data/lib/googleauth/bearer_token.rb +162 -0
- data/lib/googleauth/client_id.rb +9 -6
- data/lib/googleauth/compute_engine.rb +231 -49
- data/lib/googleauth/credentials.rb +187 -58
- data/lib/googleauth/credentials_loader.rb +11 -20
- data/lib/googleauth/default_credentials.rb +29 -8
- data/lib/googleauth/errors.rb +117 -0
- data/lib/googleauth/external_account/aws_credentials.rb +85 -18
- data/lib/googleauth/external_account/base_credentials.rb +67 -6
- data/lib/googleauth/external_account/external_account_utils.rb +15 -4
- data/lib/googleauth/external_account/identity_pool_credentials.rb +40 -15
- data/lib/googleauth/external_account/pluggable_credentials.rb +34 -19
- data/lib/googleauth/external_account.rb +32 -7
- data/lib/googleauth/helpers/connection.rb +7 -1
- data/lib/googleauth/iam.rb +19 -3
- data/lib/googleauth/id_tokens/errors.rb +13 -7
- data/lib/googleauth/id_tokens/key_sources.rb +13 -7
- data/lib/googleauth/id_tokens/verifier.rb +2 -3
- data/lib/googleauth/id_tokens.rb +4 -6
- data/lib/googleauth/impersonated_service_account.rb +329 -0
- data/lib/googleauth/json_key_reader.rb +13 -3
- data/lib/googleauth/oauth2/sts_client.rb +9 -4
- data/lib/googleauth/scope_util.rb +1 -1
- data/lib/googleauth/service_account.rb +84 -104
- data/lib/googleauth/service_account_jwt_header.rb +187 -0
- data/lib/googleauth/signet.rb +169 -4
- data/lib/googleauth/token_store.rb +3 -3
- data/lib/googleauth/user_authorizer.rb +89 -11
- data/lib/googleauth/user_refresh.rb +72 -9
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +65 -17
- data/lib/googleauth.rb +8 -0
- metadata +45 -13
@@ -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 "googleauth/errors"
|
16
|
+
|
15
17
|
module Google
|
16
18
|
# Module Auth provides classes that provide Google-specific authorization
|
17
19
|
# used to access Google APIs.
|
@@ -19,15 +21,23 @@ module Google
|
|
19
21
|
# JsonKeyReader contains the behaviour used to read private key and
|
20
22
|
# client email fields from the service account
|
21
23
|
module JsonKeyReader
|
24
|
+
# Reads a JSON key from an IO object and extracts common fields.
|
25
|
+
#
|
26
|
+
# @param json_key_io [IO] An IO object containing the JSON key
|
27
|
+
# @return [Array(String, String, String, String, String)] An array containing:
|
28
|
+
# private_key, client_email, project_id, quota_project_id, and universe_domain
|
29
|
+
# @raise [Google::Auth::InitializationError] If client_email or private_key
|
30
|
+
# fields are missing from the JSON
|
22
31
|
def read_json_key json_key_io
|
23
32
|
json_key = MultiJson.load json_key_io.read
|
24
|
-
raise "missing client_email" unless json_key.key? "client_email"
|
25
|
-
raise "missing private_key" unless json_key.key? "private_key"
|
33
|
+
raise InitializationError, "missing client_email" unless json_key.key? "client_email"
|
34
|
+
raise InitializationError, "missing private_key" unless json_key.key? "private_key"
|
26
35
|
[
|
27
36
|
json_key["private_key"],
|
28
37
|
json_key["client_email"],
|
29
38
|
json_key["project_id"],
|
30
|
-
json_key["quota_project_id"]
|
39
|
+
json_key["quota_project_id"],
|
40
|
+
json_key["universe_domain"]
|
31
41
|
]
|
32
42
|
end
|
33
43
|
end
|
@@ -12,6 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
require "googleauth/errors"
|
15
16
|
require "googleauth/helpers/connection"
|
16
17
|
|
17
18
|
module Google
|
@@ -36,10 +37,12 @@ module Google
|
|
36
37
|
|
37
38
|
# Create a new instance of the STSClient.
|
38
39
|
#
|
39
|
-
# @param [
|
40
|
-
#
|
40
|
+
# @param [Hash] options Configuration options
|
41
|
+
# @option options [String] :token_exchange_endpoint The token exchange endpoint
|
42
|
+
# @option options [Faraday::Connection] :connection The Faraday connection to use
|
43
|
+
# @raise [Google::Auth::InitializationError] If token_exchange_endpoint is nil
|
41
44
|
def initialize options = {}
|
42
|
-
raise "Token exchange endpoint can not be nil" if options[:token_exchange_endpoint].nil?
|
45
|
+
raise InitializationError, "Token exchange endpoint can not be nil" if options[:token_exchange_endpoint].nil?
|
43
46
|
self.default_connection = options[:connection]
|
44
47
|
@token_exchange_endpoint = options[:token_exchange_endpoint]
|
45
48
|
end
|
@@ -67,6 +70,8 @@ module Google
|
|
67
70
|
# The optional additional headers to pass to the token exchange endpoint.
|
68
71
|
#
|
69
72
|
# @return [Hash] A hash containing the token exchange response.
|
73
|
+
# @raise [ArgumentError] If required options are missing
|
74
|
+
# @raise [Google::Auth::AuthorizationError] If the token exchange request fails
|
70
75
|
def exchange_token options = {}
|
71
76
|
missing_required_opts = [:grant_type, :subject_token, :subject_token_type] - options.keys
|
72
77
|
unless missing_required_opts.empty?
|
@@ -81,7 +86,7 @@ module Google
|
|
81
86
|
response = connection.post @token_exchange_endpoint, URI.encode_www_form(request_body), headers
|
82
87
|
|
83
88
|
if response.status != 200
|
84
|
-
raise "Token exchange failed with status #{response.status}"
|
89
|
+
raise AuthorizationError, "Token exchange failed with status #{response.status}"
|
85
90
|
end
|
86
91
|
|
87
92
|
MultiJson.load response.body
|
@@ -57,7 +57,7 @@ module Google
|
|
57
57
|
#
|
58
58
|
# @param scope [String,Array<String>] Input scope(s)
|
59
59
|
# @return [Array<String>] Always an array of strings
|
60
|
-
# @raise ArgumentError If the input is not a string or array of strings
|
60
|
+
# @raise [ArgumentError] If the input is not a string or array of strings
|
61
61
|
#
|
62
62
|
def self.as_array scope
|
63
63
|
case scope
|
@@ -12,13 +12,16 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
require "googleauth/signet"
|
16
|
-
require "googleauth/credentials_loader"
|
17
|
-
require "googleauth/json_key_reader"
|
18
15
|
require "jwt"
|
19
16
|
require "multi_json"
|
20
17
|
require "stringio"
|
21
18
|
|
19
|
+
require "google/logging/message"
|
20
|
+
require "googleauth/signet"
|
21
|
+
require "googleauth/credentials_loader"
|
22
|
+
require "googleauth/json_key_reader"
|
23
|
+
require "googleauth/service_account_jwt_header"
|
24
|
+
|
22
25
|
module Google
|
23
26
|
# Module Auth provides classes that provide Google-specific authorization
|
24
27
|
# used to access Google APIs.
|
@@ -39,13 +42,18 @@ module Google
|
|
39
42
|
attr_reader :quota_project_id
|
40
43
|
|
41
44
|
def enable_self_signed_jwt?
|
42
|
-
|
45
|
+
# Use a self-singed JWT if there's no information that can be used to
|
46
|
+
# obtain an OAuth token, OR if there are scopes but also an assertion
|
47
|
+
# that they are default scopes that shouldn't be used to fetch a token,
|
48
|
+
# OR we are not in the default universe and thus OAuth isn't supported.
|
49
|
+
target_audience.nil? && (scope.nil? || @enable_self_signed_jwt || universe_domain != "googleapis.com")
|
43
50
|
end
|
44
51
|
|
45
52
|
# Creates a ServiceAccountCredentials.
|
46
53
|
#
|
47
|
-
# @param json_key_io [IO]
|
54
|
+
# @param json_key_io [IO] An IO object containing the JSON key
|
48
55
|
# @param scope [string|array|nil] the scope(s) to access
|
56
|
+
# @raise [ArgumentError] If both scope and target_audience are specified
|
49
57
|
def self.make_creds options = {}
|
50
58
|
json_key_io, scope, enable_self_signed_jwt, target_audience, audience, token_credential_uri =
|
51
59
|
options.values_at :json_key_io, :scope, :enable_self_signed_jwt, :target_audience,
|
@@ -53,12 +61,13 @@ module Google
|
|
53
61
|
raise ArgumentError, "Cannot specify both scope and target_audience" if scope && target_audience
|
54
62
|
|
55
63
|
if json_key_io
|
56
|
-
private_key, client_email, project_id, quota_project_id = read_json_key json_key_io
|
64
|
+
private_key, client_email, project_id, quota_project_id, universe_domain = read_json_key json_key_io
|
57
65
|
else
|
58
66
|
private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
59
67
|
client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
60
68
|
project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
|
61
69
|
quota_project_id = nil
|
70
|
+
universe_domain = nil
|
62
71
|
end
|
63
72
|
project_id ||= CredentialsLoader.load_gcloud_project_id
|
64
73
|
|
@@ -70,13 +79,41 @@ module Google
|
|
70
79
|
issuer: client_email,
|
71
80
|
signing_key: OpenSSL::PKey::RSA.new(private_key),
|
72
81
|
project_id: project_id,
|
73
|
-
quota_project_id: quota_project_id
|
82
|
+
quota_project_id: quota_project_id,
|
83
|
+
universe_domain: universe_domain || "googleapis.com")
|
74
84
|
.configure_connection(options)
|
75
85
|
end
|
76
86
|
|
87
|
+
# Creates a duplicate of these credentials
|
88
|
+
# without the Signet::OAuth2::Client-specific
|
89
|
+
# transient state (e.g. cached tokens)
|
90
|
+
#
|
91
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
92
|
+
# The following keys are recognized in addition to keys in the
|
93
|
+
# Signet::OAuth2::Client
|
94
|
+
# * `:enable_self_signed_jwt` Whether the self-signed JWT should
|
95
|
+
# be used for the authentication
|
96
|
+
# * `project_id` the project id to use during the authentication
|
97
|
+
# * `quota_project_id` the quota project id to use
|
98
|
+
# during the authentication
|
99
|
+
def duplicate options = {}
|
100
|
+
options = deep_hash_normalize options
|
101
|
+
super(
|
102
|
+
{
|
103
|
+
enable_self_signed_jwt: @enable_self_signed_jwt,
|
104
|
+
project_id: project_id,
|
105
|
+
quota_project_id: quota_project_id,
|
106
|
+
logger: logger
|
107
|
+
}.merge(options)
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
77
111
|
# Handles certain escape sequences that sometimes appear in input.
|
78
112
|
# Specifically, interprets the "\n" sequence for newline, and removes
|
79
113
|
# enclosing quotes.
|
114
|
+
#
|
115
|
+
# @param str [String] The string to unescape
|
116
|
+
# @return [String] The unescaped string
|
80
117
|
def self.unescape str
|
81
118
|
str = str.gsub '\n', "\n"
|
82
119
|
str = str[1..-2] if str.start_with?('"') && str.end_with?('"')
|
@@ -93,16 +130,51 @@ module Google
|
|
93
130
|
# Extends the base class to use a transient
|
94
131
|
# ServiceAccountJwtHeaderCredentials for certain cases.
|
95
132
|
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?)
|
133
|
+
if enable_self_signed_jwt?
|
100
134
|
apply_self_signed_jwt! a_hash
|
101
135
|
else
|
102
136
|
super
|
103
137
|
end
|
104
138
|
end
|
105
139
|
|
140
|
+
# Modifies this logic so it also requires self-signed-jwt to be disabled
|
141
|
+
def needs_access_token?
|
142
|
+
super && !enable_self_signed_jwt?
|
143
|
+
end
|
144
|
+
|
145
|
+
# Destructively updates these credentials
|
146
|
+
#
|
147
|
+
# This method is called by `Signet::OAuth2::Client`'s constructor
|
148
|
+
#
|
149
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
150
|
+
# The following keys are recognized in addition to keys in the
|
151
|
+
# Signet::OAuth2::Client
|
152
|
+
# * `:enable_self_signed_jwt` Whether the self-signed JWT should
|
153
|
+
# be used for the authentication
|
154
|
+
# * `project_id` the project id to use during the authentication
|
155
|
+
# * `quota_project_id` the quota project id to use
|
156
|
+
# during the authentication
|
157
|
+
# @return [Google::Auth::ServiceAccountCredentials]
|
158
|
+
def update! options = {}
|
159
|
+
# Normalize all keys to symbols to allow indifferent access.
|
160
|
+
options = deep_hash_normalize options
|
161
|
+
|
162
|
+
@enable_self_signed_jwt = options[:enable_self_signed_jwt] ? true : false
|
163
|
+
@project_id = options[:project_id] if options.key? :project_id
|
164
|
+
@quota_project_id = options[:quota_project_id] if options.key? :quota_project_id
|
165
|
+
|
166
|
+
super(options)
|
167
|
+
|
168
|
+
self
|
169
|
+
end
|
170
|
+
|
171
|
+
# Returns the client email as the principal for service account credentials
|
172
|
+
# @private
|
173
|
+
# @return [String] the email address of the service account
|
174
|
+
def principal
|
175
|
+
@issuer
|
176
|
+
end
|
177
|
+
|
106
178
|
private
|
107
179
|
|
108
180
|
def apply_self_signed_jwt! a_hash
|
@@ -115,101 +187,9 @@ module Google
|
|
115
187
|
}
|
116
188
|
key_io = StringIO.new MultiJson.dump(cred_json)
|
117
189
|
alt = ServiceAccountJwtHeaderCredentials.make_creds json_key_io: key_io, scope: scope
|
190
|
+
alt.logger = logger
|
118
191
|
alt.apply! a_hash
|
119
192
|
end
|
120
193
|
end
|
121
|
-
|
122
|
-
# Authenticates requests using Google's Service Account credentials via
|
123
|
-
# JWT Header.
|
124
|
-
#
|
125
|
-
# This class allows authorizing requests for service accounts directly
|
126
|
-
# from credentials from a json key file downloaded from the developer
|
127
|
-
# console (via 'Generate new Json Key'). It is not part of any OAuth2
|
128
|
-
# flow, rather it creates a JWT and sends that as a credential.
|
129
|
-
#
|
130
|
-
# cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
|
131
|
-
class ServiceAccountJwtHeaderCredentials
|
132
|
-
JWT_AUD_URI_KEY = :jwt_aud_uri
|
133
|
-
AUTH_METADATA_KEY = Google::Auth::BaseClient::AUTH_METADATA_KEY
|
134
|
-
TOKEN_CRED_URI = "https://www.googleapis.com/oauth2/v4/token".freeze
|
135
|
-
SIGNING_ALGORITHM = "RS256".freeze
|
136
|
-
EXPIRY = 60
|
137
|
-
extend CredentialsLoader
|
138
|
-
extend JsonKeyReader
|
139
|
-
attr_reader :project_id
|
140
|
-
attr_reader :quota_project_id
|
141
|
-
|
142
|
-
# Create a ServiceAccountJwtHeaderCredentials.
|
143
|
-
#
|
144
|
-
# @param json_key_io [IO] an IO from which the JSON key can be read
|
145
|
-
# @param scope [string|array|nil] the scope(s) to access
|
146
|
-
def self.make_creds options = {}
|
147
|
-
json_key_io, scope = options.values_at :json_key_io, :scope
|
148
|
-
new json_key_io: json_key_io, scope: scope
|
149
|
-
end
|
150
|
-
|
151
|
-
# Initializes a ServiceAccountJwtHeaderCredentials.
|
152
|
-
#
|
153
|
-
# @param json_key_io [IO] an IO from which the JSON key can be read
|
154
|
-
def initialize options = {}
|
155
|
-
json_key_io = options[:json_key_io]
|
156
|
-
if json_key_io
|
157
|
-
@private_key, @issuer, @project_id, @quota_project_id =
|
158
|
-
self.class.read_json_key json_key_io
|
159
|
-
else
|
160
|
-
@private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
161
|
-
@issuer = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
162
|
-
@project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
|
163
|
-
@quota_project_id = nil
|
164
|
-
end
|
165
|
-
@project_id ||= CredentialsLoader.load_gcloud_project_id
|
166
|
-
@signing_key = OpenSSL::PKey::RSA.new @private_key
|
167
|
-
@scope = options[:scope]
|
168
|
-
end
|
169
|
-
|
170
|
-
# Construct a jwt token if the JWT_AUD_URI key is present in the input
|
171
|
-
# hash.
|
172
|
-
#
|
173
|
-
# The jwt token is used as the value of a 'Bearer '.
|
174
|
-
def apply! a_hash, opts = {}
|
175
|
-
jwt_aud_uri = a_hash.delete JWT_AUD_URI_KEY
|
176
|
-
return a_hash if jwt_aud_uri.nil? && @scope.nil?
|
177
|
-
jwt_token = new_jwt_token jwt_aud_uri, opts
|
178
|
-
a_hash[AUTH_METADATA_KEY] = "Bearer #{jwt_token}"
|
179
|
-
a_hash
|
180
|
-
end
|
181
|
-
|
182
|
-
# Returns a clone of a_hash updated with the authoriation header
|
183
|
-
def apply a_hash, opts = {}
|
184
|
-
a_copy = a_hash.clone
|
185
|
-
apply! a_copy, opts
|
186
|
-
a_copy
|
187
|
-
end
|
188
|
-
|
189
|
-
# Returns a reference to the #apply method, suitable for passing as
|
190
|
-
# a closure
|
191
|
-
def updater_proc
|
192
|
-
proc { |a_hash, opts = {}| apply a_hash, opts }
|
193
|
-
end
|
194
|
-
|
195
|
-
# Creates a jwt uri token.
|
196
|
-
def new_jwt_token jwt_aud_uri = nil, options = {}
|
197
|
-
now = Time.new
|
198
|
-
skew = options[:skew] || 60
|
199
|
-
assertion = {
|
200
|
-
"iss" => @issuer,
|
201
|
-
"sub" => @issuer,
|
202
|
-
"exp" => (now + EXPIRY).to_i,
|
203
|
-
"iat" => (now - skew).to_i
|
204
|
-
}
|
205
|
-
|
206
|
-
jwt_aud_uri = nil if @scope
|
207
|
-
|
208
|
-
assertion["scope"] = Array(@scope).join " " if @scope
|
209
|
-
assertion["aud"] = jwt_aud_uri if jwt_aud_uri
|
210
|
-
|
211
|
-
JWT.encode assertion, @signing_key, SIGNING_ALGORITHM
|
212
|
-
end
|
213
|
-
end
|
214
194
|
end
|
215
195
|
end
|
@@ -0,0 +1,187 @@
|
|
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 object containing the JSON key
|
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 object containing the JSON key
|
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
|
+
# Returns the client email as the principal for service account JWT header credentials
|
163
|
+
# @private
|
164
|
+
# @return [String] the email address of the service account
|
165
|
+
def principal
|
166
|
+
@issuer
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def deep_hash_normalize old_hash
|
172
|
+
sym_hash = {}
|
173
|
+
old_hash&.each { |k, v| sym_hash[k.to_sym] = recursive_hash_normalize_keys v }
|
174
|
+
sym_hash
|
175
|
+
end
|
176
|
+
|
177
|
+
# Convert all keys in this hash (nested) to symbols for uniform retrieval
|
178
|
+
def recursive_hash_normalize_keys val
|
179
|
+
if val.is_a? Hash
|
180
|
+
deep_hash_normalize val
|
181
|
+
else
|
182
|
+
val
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|