googleauth 1.13.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 +14 -0
- data/README.md +1 -1
- data/lib/googleauth/api_key.rb +155 -0
- data/lib/googleauth/bearer_token.rb +148 -0
- data/lib/googleauth/compute_engine.rb +1 -1
- data/lib/googleauth/credentials_loader.rb +2 -2
- data/lib/googleauth/default_credentials.rb +2 -2
- data/lib/googleauth/helpers/connection.rb +7 -1
- data/lib/googleauth/service_account.rb +5 -159
- data/lib/googleauth/service_account_jwt_header.rb +180 -0
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth.rb +7 -0
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e2eef22c1062f2fd2216760e90a1af9ece1b99838bccea486a7b4ce43be9734
|
4
|
+
data.tar.gz: 1e60ef4856de1e4f7a3d423f3953e5e39f86c7002216ad2d98ee46ec88aaaf25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5a87f962d04eb59c9ae083a4d3c46fd54ef4d4c53f0571b3952f6d1a9d30e3af89fb2c50e04b118e301097850c9985c713968dc29c62c5d388d0d8a4c3d306d
|
7
|
+
data.tar.gz: fae67434766ed2d4bb67e2c7ba2784a9397843110c28d96368d368b1dd30229a1ea83c5ee05e70a712446781e67ca84149fbaa72dd4239eb349d6943ff42b9b3
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Release History
|
2
2
|
|
3
|
+
### 1.14.0 (2025-03-14)
|
4
|
+
|
5
|
+
#### Features
|
6
|
+
|
7
|
+
* add API key credentials ([#520](https://github.com/googleapis/google-auth-library-ruby/issues/520))
|
8
|
+
* Add Bearer token credentials
|
9
|
+
* add BearerToken credentials ([#522](https://github.com/googleapis/google-auth-library-ruby/issues/522))
|
10
|
+
* Update minimum Ruby version to 3.0 ([#527](https://github.com/googleapis/google-auth-library-ruby/issues/527))
|
11
|
+
#### Bug Fixes
|
12
|
+
|
13
|
+
* Eliminated the "attribute accessor as module_function" warning ([#519](https://github.com/googleapis/google-auth-library-ruby/issues/519))
|
14
|
+
* Get the project_id from gcloud ([#479](https://github.com/googleapis/google-auth-library-ruby/issues/479))
|
15
|
+
* logger configuration in service account JWT header ([#525](https://github.com/googleapis/google-auth-library-ruby/issues/525))
|
16
|
+
|
3
17
|
### 1.13.1 (2025-01-24)
|
4
18
|
|
5
19
|
#### Bug Fixes
|
data/README.md
CHANGED
@@ -265,7 +265,7 @@ Custom storage implementations can also be used. See
|
|
265
265
|
|
266
266
|
## Supported Ruby Versions
|
267
267
|
|
268
|
-
This library is supported on Ruby
|
268
|
+
This library is supported on Ruby 3.0+.
|
269
269
|
|
270
270
|
Google provides official support for Ruby versions that are actively supported
|
271
271
|
by Ruby Core—that is, Ruby versions that are either in normal maintenance or
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
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/base_client"
|
16
|
+
require "googleauth/credentials_loader"
|
17
|
+
|
18
|
+
module Google
|
19
|
+
module Auth
|
20
|
+
##
|
21
|
+
# Implementation of Google API Key authentication.
|
22
|
+
#
|
23
|
+
# API Keys are text strings. They don't have an associated JSON file.
|
24
|
+
#
|
25
|
+
# The end-user is managing their API Keys directly, not via
|
26
|
+
# an authentication library.
|
27
|
+
#
|
28
|
+
# API Keys provide project information for an API request.
|
29
|
+
# API Keys don't reference an IAM principal, they do not expire,
|
30
|
+
# and cannot be refreshed.
|
31
|
+
#
|
32
|
+
class APIKeyCredentials
|
33
|
+
include Google::Auth::BaseClient
|
34
|
+
|
35
|
+
# @private Authorization header key
|
36
|
+
API_KEY_HEADER = "x-goog-api-key".freeze
|
37
|
+
|
38
|
+
# @private Environment variable containing API key
|
39
|
+
API_KEY_VAR = "GOOGLE_API_KEY".freeze
|
40
|
+
|
41
|
+
# @return [String] The API key
|
42
|
+
attr_reader :api_key
|
43
|
+
|
44
|
+
# @return [String] The universe domain of the universe
|
45
|
+
# this API key is for
|
46
|
+
attr_accessor :universe_domain
|
47
|
+
|
48
|
+
class << self
|
49
|
+
# Creates an APIKeyCredentials from the environment.
|
50
|
+
# Checks the ENV['GOOGLE_API_KEY'] variable.
|
51
|
+
#
|
52
|
+
# @param [String] _scope
|
53
|
+
# The scope to use for OAuth. Not used by API key auth.
|
54
|
+
# @param [Hash] options
|
55
|
+
# The options to pass to the credentials instance
|
56
|
+
#
|
57
|
+
# @return [Google::Auth::APIKeyCredentials, nil]
|
58
|
+
# Credentials if the API key environment variable is present,
|
59
|
+
# nil otherwise
|
60
|
+
def from_env _scope = nil, options = {}
|
61
|
+
api_key = ENV[API_KEY_VAR]
|
62
|
+
return nil if api_key.nil? || api_key.empty?
|
63
|
+
new options.merge(api_key: api_key)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Create the APIKeyCredentials.
|
67
|
+
#
|
68
|
+
# @param [Hash] options The credentials options
|
69
|
+
# @option options [String] :api_key
|
70
|
+
# The API key to use for authentication
|
71
|
+
# @option options [String] :universe_domain
|
72
|
+
# The universe domain of the universe this API key
|
73
|
+
# belongs to (defaults to googleapis.com)
|
74
|
+
# @return [Google::Auth::APIKeyCredentials]
|
75
|
+
def make_creds options = {}
|
76
|
+
new options
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Initialize the APIKeyCredentials.
|
81
|
+
#
|
82
|
+
# @param [Hash] options The credentials options
|
83
|
+
# @option options [String] :api_key
|
84
|
+
# The API key to use for authentication
|
85
|
+
# @option options [String] :universe_domain
|
86
|
+
# The universe domain of the universe this API key
|
87
|
+
# belongs to (defaults to googleapis.com)
|
88
|
+
def initialize options = {}
|
89
|
+
raise ArgumentError, "API key must be provided" if options[:api_key].nil? || options[:api_key].empty?
|
90
|
+
@api_key = options[:api_key]
|
91
|
+
@universe_domain = options[:universe_domain] || "googleapis.com"
|
92
|
+
end
|
93
|
+
|
94
|
+
# Determines if the credentials object has expired.
|
95
|
+
# Since API keys don't expire, this always returns false.
|
96
|
+
#
|
97
|
+
# @param [Fixnum] _seconds
|
98
|
+
# The optional timeout in seconds since the last refresh
|
99
|
+
# @return [Boolean]
|
100
|
+
# True if the token has expired, false otherwise.
|
101
|
+
def expires_within? _seconds
|
102
|
+
false
|
103
|
+
end
|
104
|
+
|
105
|
+
# Creates a duplicate of these credentials.
|
106
|
+
#
|
107
|
+
# @param [Hash] options Additional options for configuring the credentials
|
108
|
+
# @return [Google::Auth::APIKeyCredentials]
|
109
|
+
def duplicate options = {}
|
110
|
+
self.class.new(
|
111
|
+
api_key: options[:api_key] || @api_key,
|
112
|
+
universe_domain: options[:universe_domain] || @universe_domain
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Updates the provided hash with the API Key header.
|
117
|
+
#
|
118
|
+
# The `apply!` method modifies the provided hash in place, adding the
|
119
|
+
# `x-goog-api-key` header with the API Key value.
|
120
|
+
#
|
121
|
+
# The API Key is hashed before being logged for security purposes.
|
122
|
+
#
|
123
|
+
# NB: this method typically would be called through `updater_proc`.
|
124
|
+
# Some older clients call it directly though, so it has to be public.
|
125
|
+
#
|
126
|
+
# @param [Hash] a_hash The hash to which the API Key header should be added.
|
127
|
+
# This is typically a hash representing the request headers. This hash
|
128
|
+
# will be modified in place.
|
129
|
+
# @param [Hash] _opts Additional options (currently not used). Included
|
130
|
+
# for consistency with the `BaseClient` interface.
|
131
|
+
# @return [Hash] The modified hash (the same hash passed as the `a_hash`
|
132
|
+
# argument).
|
133
|
+
def apply! a_hash, _opts = {}
|
134
|
+
a_hash[API_KEY_HEADER] = @api_key
|
135
|
+
logger&.debug do
|
136
|
+
hash = Digest::SHA256.hexdigest @api_key
|
137
|
+
Google::Logging::Message.from message: "Sending API key auth token. (sha256:#{hash})"
|
138
|
+
end
|
139
|
+
a_hash
|
140
|
+
end
|
141
|
+
|
142
|
+
protected
|
143
|
+
|
144
|
+
# The token type should be :api_key
|
145
|
+
def token_type
|
146
|
+
:api_key
|
147
|
+
end
|
148
|
+
|
149
|
+
# We don't need to fetch access tokens for API key auth
|
150
|
+
def fetch_access_token! _options = {}
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
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/base_client"
|
16
|
+
|
17
|
+
module Google
|
18
|
+
module Auth
|
19
|
+
##
|
20
|
+
# Implementation of Bearer Token authentication scenario.
|
21
|
+
#
|
22
|
+
# Bearer tokens are strings representing an authorization grant.
|
23
|
+
# They can be OAuth2 ("ya.29") tokens, JWTs, IDTokens -- anything
|
24
|
+
# that is sent as a `Bearer` in an `Authorization` header.
|
25
|
+
#
|
26
|
+
# Not all 'authentication' strings can be used with this class,
|
27
|
+
# e.g. an API key cannot since API keys are sent in a
|
28
|
+
# `x-goog-api-key` header or as a query parameter.
|
29
|
+
#
|
30
|
+
# This class should be used when the end-user is managing the
|
31
|
+
# authentication token separately, e.g. with a separate service.
|
32
|
+
# This means that tasks like tracking the lifetime of and
|
33
|
+
# refreshing the token are outside the scope of this class.
|
34
|
+
#
|
35
|
+
# There is no JSON representation for this type of credentials.
|
36
|
+
# If the end-user has credentials in JSON format they should typically
|
37
|
+
# use the corresponding credentials type, e.g. ServiceAccountCredentials
|
38
|
+
# with the service account JSON.
|
39
|
+
#
|
40
|
+
class BearerTokenCredentials
|
41
|
+
include Google::Auth::BaseClient
|
42
|
+
|
43
|
+
# @private Authorization header name
|
44
|
+
AUTH_METADATA_KEY = Google::Auth::BaseClient::AUTH_METADATA_KEY
|
45
|
+
|
46
|
+
# @return [String] The token to be sent as a part of Bearer claim
|
47
|
+
attr_reader :token
|
48
|
+
# The following aliasing is needed for BaseClient since it sends :token_type
|
49
|
+
alias bearer_token token
|
50
|
+
|
51
|
+
# @return [Time, nil] The token expiration time provided by the end-user.
|
52
|
+
attr_reader :expires_at
|
53
|
+
|
54
|
+
# @return [String] The universe domain of the universe
|
55
|
+
# this token is for
|
56
|
+
attr_accessor :universe_domain
|
57
|
+
|
58
|
+
class << self
|
59
|
+
# Create the BearerTokenCredentials.
|
60
|
+
#
|
61
|
+
# @param [Hash] options The credentials options
|
62
|
+
# @option options [String] :token The bearer token to use.
|
63
|
+
# @option options [Time, Numeric, nil] :expires_at The token expiration time provided by the end-user.
|
64
|
+
# Optional, for the end-user's convenience. Can be a Time object, a number of seconds since epoch.
|
65
|
+
# If `expires_at` is `nil`, it is treated as "token never expires".
|
66
|
+
# @option options [String] :universe_domain The universe domain of the universe
|
67
|
+
# this token is for (defaults to googleapis.com)
|
68
|
+
# @return [Google::Auth::BearerTokenCredentials]
|
69
|
+
def make_creds options = {}
|
70
|
+
new options
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Initialize the BearerTokenCredentials.
|
75
|
+
#
|
76
|
+
# @param [Hash] options The credentials options
|
77
|
+
# @option options [String] :token The bearer token to use.
|
78
|
+
# @option options [Time, Numeric, nil] :expires_at The token expiration time provided by the end-user.
|
79
|
+
# Optional, for the end-user's convenience. Can be a Time object, a number of seconds since epoch.
|
80
|
+
# If `expires_at` is `nil`, it is treated as "token never expires".
|
81
|
+
# @option options [String] :universe_domain The universe domain of the universe
|
82
|
+
# this token is for (defaults to googleapis.com)
|
83
|
+
def initialize options = {}
|
84
|
+
raise ArgumentError, "Bearer token must be provided" if options[:token].nil? || options[:token].empty?
|
85
|
+
@token = options[:token]
|
86
|
+
@expires_at = case options[:expires_at]
|
87
|
+
when Time
|
88
|
+
options[:expires_at]
|
89
|
+
when Numeric
|
90
|
+
Time.at options[:expires_at]
|
91
|
+
end
|
92
|
+
|
93
|
+
@universe_domain = options[:universe_domain] || "googleapis.com"
|
94
|
+
end
|
95
|
+
|
96
|
+
# Determines if the credentials object has expired.
|
97
|
+
#
|
98
|
+
# @param [Numeric] seconds The optional timeout in seconds.
|
99
|
+
# @return [Boolean] True if the token has expired, false otherwise, or
|
100
|
+
# if the expires_at was not provided.
|
101
|
+
def expires_within? seconds
|
102
|
+
return false if @expires_at.nil? # Treat nil expiration as "never expires"
|
103
|
+
Time.now + seconds >= @expires_at
|
104
|
+
end
|
105
|
+
|
106
|
+
# Creates a duplicate of these credentials.
|
107
|
+
#
|
108
|
+
# @param [Hash] options Additional options for configuring the credentials
|
109
|
+
# @option options [String] :token The bearer token to use.
|
110
|
+
# @option options [Time, Numeric] :expires_at The token expiration time. Can be a Time
|
111
|
+
# object or a number of seconds since epoch.
|
112
|
+
# @option options [String] :universe_domain The universe domain (defaults to googleapis.com)
|
113
|
+
# @return [Google::Auth::BearerTokenCredentials]
|
114
|
+
def duplicate options = {}
|
115
|
+
self.class.new(
|
116
|
+
token: options[:token] || @token,
|
117
|
+
expires_at: options[:expires_at] || @expires_at,
|
118
|
+
universe_domain: options[:universe_domain] || @universe_domain
|
119
|
+
)
|
120
|
+
end
|
121
|
+
|
122
|
+
protected
|
123
|
+
|
124
|
+
##
|
125
|
+
# BearerTokenCredentials do not support fetching a new token.
|
126
|
+
#
|
127
|
+
# If the token has an expiration time and is expired, this method will
|
128
|
+
# raise an error.
|
129
|
+
#
|
130
|
+
# @param [Hash] _options Options for fetching a new token (not used).
|
131
|
+
# @return [nil] Always returns nil.
|
132
|
+
# @raise [StandardError] If the token is expired.
|
133
|
+
def fetch_access_token! _options = {}
|
134
|
+
if @expires_at && Time.now >= @expires_at
|
135
|
+
raise "Bearer token has expired."
|
136
|
+
end
|
137
|
+
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def token_type
|
144
|
+
:bearer_token
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -87,7 +87,7 @@ module Google
|
|
87
87
|
def initialize options = {}
|
88
88
|
# Override the constructor to remember whether the universe domain was
|
89
89
|
# overridden by a constructor argument.
|
90
|
-
@universe_domain_overridden = options["universe_domain"] || options[:universe_domain]
|
90
|
+
@universe_domain_overridden = options["universe_domain"] || options[:universe_domain]
|
91
91
|
# TODO: Remove when universe domain metadata endpoint is stable (see b/349488459).
|
92
92
|
@disable_universe_domain_check = true
|
93
93
|
super options
|
@@ -37,7 +37,7 @@ module Google
|
|
37
37
|
AWS_SESSION_TOKEN_VAR = "AWS_SESSION_TOKEN".freeze
|
38
38
|
GCLOUD_POSIX_COMMAND = "gcloud".freeze
|
39
39
|
GCLOUD_WINDOWS_COMMAND = "gcloud.cmd".freeze
|
40
|
-
GCLOUD_CONFIG_COMMAND = "config config-helper --format json --verbosity none".freeze
|
40
|
+
GCLOUD_CONFIG_COMMAND = "config config-helper --format json --verbosity none --quiet".freeze
|
41
41
|
|
42
42
|
CREDENTIALS_FILE_NAME = "application_default_credentials.json".freeze
|
43
43
|
NOT_FOUND_ERROR = "Unable to read the credential file specified by #{ENV_VAR}".freeze
|
@@ -146,7 +146,7 @@ module Google
|
|
146
146
|
def load_gcloud_project_id
|
147
147
|
gcloud = GCLOUD_WINDOWS_COMMAND if OS.windows?
|
148
148
|
gcloud = GCLOUD_POSIX_COMMAND unless OS.windows?
|
149
|
-
gcloud_json = IO.popen("#{gcloud} #{GCLOUD_CONFIG_COMMAND}",
|
149
|
+
gcloud_json = IO.popen("#{gcloud} #{GCLOUD_CONFIG_COMMAND}", err: :close, &:read)
|
150
150
|
config = MultiJson.load gcloud_json
|
151
151
|
config["configuration"]["properties"]["core"]["project"]
|
152
152
|
rescue StandardError
|
@@ -16,10 +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
|
-
require "googleauth/impersonated_service_account"
|
23
23
|
|
24
24
|
module Google
|
25
25
|
# Module Auth provides classes that provide Google-specific authorization
|
@@ -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
|
@@ -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
|
@@ -178,161 +180,5 @@ module Google
|
|
178
180
|
alt.apply! a_hash
|
179
181
|
end
|
180
182
|
end
|
181
|
-
|
182
|
-
# Authenticates requests using Google's Service Account credentials via
|
183
|
-
# JWT Header.
|
184
|
-
#
|
185
|
-
# This class allows authorizing requests for service accounts directly
|
186
|
-
# from credentials from a json key file downloaded from the developer
|
187
|
-
# console (via 'Generate new Json Key'). It is not part of any OAuth2
|
188
|
-
# flow, rather it creates a JWT and sends that as a credential.
|
189
|
-
#
|
190
|
-
# cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
|
191
|
-
class ServiceAccountJwtHeaderCredentials
|
192
|
-
JWT_AUD_URI_KEY = :jwt_aud_uri
|
193
|
-
AUTH_METADATA_KEY = Google::Auth::BaseClient::AUTH_METADATA_KEY
|
194
|
-
TOKEN_CRED_URI = "https://www.googleapis.com/oauth2/v4/token".freeze
|
195
|
-
SIGNING_ALGORITHM = "RS256".freeze
|
196
|
-
EXPIRY = 60
|
197
|
-
|
198
|
-
extend CredentialsLoader
|
199
|
-
extend JsonKeyReader
|
200
|
-
|
201
|
-
attr_reader :project_id
|
202
|
-
attr_reader :quota_project_id
|
203
|
-
attr_accessor :universe_domain
|
204
|
-
attr_accessor :logger
|
205
|
-
|
206
|
-
# Create a ServiceAccountJwtHeaderCredentials.
|
207
|
-
#
|
208
|
-
# @param json_key_io [IO] an IO from which the JSON key can be read
|
209
|
-
# @param scope [string|array|nil] the scope(s) to access
|
210
|
-
def self.make_creds options = {}
|
211
|
-
json_key_io, scope = options.values_at :json_key_io, :scope
|
212
|
-
new json_key_io: json_key_io, scope: scope
|
213
|
-
end
|
214
|
-
|
215
|
-
# Initializes a ServiceAccountJwtHeaderCredentials.
|
216
|
-
#
|
217
|
-
# @param json_key_io [IO] an IO from which the JSON key can be read
|
218
|
-
def initialize options = {}
|
219
|
-
json_key_io = options[:json_key_io]
|
220
|
-
if json_key_io
|
221
|
-
@private_key, @issuer, @project_id, @quota_project_id, @universe_domain =
|
222
|
-
self.class.read_json_key json_key_io
|
223
|
-
else
|
224
|
-
@private_key = options.key?(:private_key) ? options[:private_key] : ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
225
|
-
@issuer = options.key?(:issuer) ? options[:issuer] : ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
226
|
-
@project_id = options.key?(:project_id) ? options[:project_id] : ENV[CredentialsLoader::PROJECT_ID_VAR]
|
227
|
-
@quota_project_id = options[:quota_project_id] if options.key? :quota_project_id
|
228
|
-
@universe_domain = options[:universe_domain] if options.key? :universe_domain
|
229
|
-
end
|
230
|
-
@universe_domain ||= "googleapis.com"
|
231
|
-
@project_id ||= CredentialsLoader.load_gcloud_project_id
|
232
|
-
@signing_key = OpenSSL::PKey::RSA.new @private_key
|
233
|
-
@scope = options[:scope] if options.key? :scope
|
234
|
-
@logger = options[:logger] if options.key? :scope
|
235
|
-
end
|
236
|
-
|
237
|
-
# Creates a duplicate of these credentials
|
238
|
-
#
|
239
|
-
# @param options [Hash] Overrides for the credentials parameters.
|
240
|
-
# The following keys are recognized
|
241
|
-
# * `private key` the private key in string form
|
242
|
-
# * `issuer` the SA issuer
|
243
|
-
# * `scope` the scope(s) to access
|
244
|
-
# * `project_id` the project id to use during the authentication
|
245
|
-
# * `quota_project_id` the quota project id to use
|
246
|
-
# * `universe_domain` the universe domain of the credentials
|
247
|
-
def duplicate options = {}
|
248
|
-
options = deep_hash_normalize options
|
249
|
-
|
250
|
-
options = {
|
251
|
-
private_key: @private_key,
|
252
|
-
issuer: @issuer,
|
253
|
-
scope: @scope,
|
254
|
-
project_id: project_id,
|
255
|
-
quota_project_id: quota_project_id,
|
256
|
-
universe_domain: universe_domain,
|
257
|
-
logger: logger
|
258
|
-
}.merge(options)
|
259
|
-
|
260
|
-
self.class.new options
|
261
|
-
end
|
262
|
-
|
263
|
-
# Construct a jwt token if the JWT_AUD_URI key is present in the input
|
264
|
-
# hash.
|
265
|
-
#
|
266
|
-
# The jwt token is used as the value of a 'Bearer '.
|
267
|
-
def apply! a_hash, opts = {}
|
268
|
-
jwt_aud_uri = a_hash.delete JWT_AUD_URI_KEY
|
269
|
-
return a_hash if jwt_aud_uri.nil? && @scope.nil?
|
270
|
-
jwt_token = new_jwt_token jwt_aud_uri, opts
|
271
|
-
a_hash[AUTH_METADATA_KEY] = "Bearer #{jwt_token}"
|
272
|
-
logger&.debug do
|
273
|
-
hash = Digest::SHA256.hexdigest jwt_token
|
274
|
-
Google::Logging::Message.from message: "Sending JWT auth token. (sha256:#{hash})"
|
275
|
-
end
|
276
|
-
a_hash
|
277
|
-
end
|
278
|
-
|
279
|
-
# Returns a clone of a_hash updated with the authorization header
|
280
|
-
def apply a_hash, opts = {}
|
281
|
-
a_copy = a_hash.clone
|
282
|
-
apply! a_copy, opts
|
283
|
-
a_copy
|
284
|
-
end
|
285
|
-
|
286
|
-
# Returns a reference to the #apply method, suitable for passing as
|
287
|
-
# a closure
|
288
|
-
def updater_proc
|
289
|
-
proc { |a_hash, opts = {}| apply a_hash, opts }
|
290
|
-
end
|
291
|
-
|
292
|
-
# Creates a jwt uri token.
|
293
|
-
def new_jwt_token jwt_aud_uri = nil, options = {}
|
294
|
-
now = Time.new
|
295
|
-
skew = options[:skew] || 60
|
296
|
-
assertion = {
|
297
|
-
"iss" => @issuer,
|
298
|
-
"sub" => @issuer,
|
299
|
-
"exp" => (now + EXPIRY).to_i,
|
300
|
-
"iat" => (now - skew).to_i
|
301
|
-
}
|
302
|
-
|
303
|
-
jwt_aud_uri = nil if @scope
|
304
|
-
|
305
|
-
assertion["scope"] = Array(@scope).join " " if @scope
|
306
|
-
assertion["aud"] = jwt_aud_uri if jwt_aud_uri
|
307
|
-
|
308
|
-
logger&.debug do
|
309
|
-
Google::Logging::Message.from message: "JWT assertion: #{assertion}"
|
310
|
-
end
|
311
|
-
|
312
|
-
JWT.encode assertion, @signing_key, SIGNING_ALGORITHM
|
313
|
-
end
|
314
|
-
|
315
|
-
# Duck-types the corresponding method from BaseClient
|
316
|
-
def needs_access_token?
|
317
|
-
false
|
318
|
-
end
|
319
|
-
|
320
|
-
private
|
321
|
-
|
322
|
-
def deep_hash_normalize old_hash
|
323
|
-
sym_hash = {}
|
324
|
-
old_hash&.each { |k, v| sym_hash[k.to_sym] = recursive_hash_normalize_keys v }
|
325
|
-
sym_hash
|
326
|
-
end
|
327
|
-
|
328
|
-
# Convert all keys in this hash (nested) to symbols for uniform retrieval
|
329
|
-
def recursive_hash_normalize_keys val
|
330
|
-
if val.is_a? Hash
|
331
|
-
deep_hash_normalize val
|
332
|
-
else
|
333
|
-
val
|
334
|
-
end
|
335
|
-
end
|
336
|
-
end
|
337
183
|
end
|
338
184
|
end
|
@@ -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/version.rb
CHANGED
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"
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: googleauth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Google LLC
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-03-14 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: faraday
|
@@ -134,7 +134,7 @@ dependencies:
|
|
134
134
|
description: Implements simple authorization for accessing Google APIs, and provides
|
135
135
|
support for Application Default Credentials.
|
136
136
|
email:
|
137
|
-
-
|
137
|
+
- googleapis-packages@google.com
|
138
138
|
executables: []
|
139
139
|
extensions: []
|
140
140
|
extra_rdoc_files: []
|
@@ -146,8 +146,10 @@ files:
|
|
146
146
|
- README.md
|
147
147
|
- SECURITY.md
|
148
148
|
- lib/googleauth.rb
|
149
|
+
- lib/googleauth/api_key.rb
|
149
150
|
- lib/googleauth/application_default.rb
|
150
151
|
- lib/googleauth/base_client.rb
|
152
|
+
- lib/googleauth/bearer_token.rb
|
151
153
|
- lib/googleauth/client_id.rb
|
152
154
|
- lib/googleauth/compute_engine.rb
|
153
155
|
- lib/googleauth/credentials.rb
|
@@ -170,6 +172,7 @@ files:
|
|
170
172
|
- lib/googleauth/oauth2/sts_client.rb
|
171
173
|
- lib/googleauth/scope_util.rb
|
172
174
|
- lib/googleauth/service_account.rb
|
175
|
+
- lib/googleauth/service_account_jwt_header.rb
|
173
176
|
- lib/googleauth/signet.rb
|
174
177
|
- lib/googleauth/stores/file_token_store.rb
|
175
178
|
- lib/googleauth/stores/redis_token_store.rb
|
@@ -192,14 +195,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
192
195
|
requirements:
|
193
196
|
- - ">="
|
194
197
|
- !ruby/object:Gem::Version
|
195
|
-
version: '
|
198
|
+
version: '3.0'
|
196
199
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
197
200
|
requirements:
|
198
201
|
- - ">="
|
199
202
|
- !ruby/object:Gem::Version
|
200
203
|
version: '0'
|
201
204
|
requirements: []
|
202
|
-
rubygems_version: 3.6.
|
205
|
+
rubygems_version: 3.6.5
|
203
206
|
specification_version: 4
|
204
207
|
summary: Google Auth Library for Ruby
|
205
208
|
test_files: []
|