googleauth 1.9.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.
@@ -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
 
@@ -29,11 +31,29 @@ module Signet
29
31
 
30
32
  def update_token! options = {}
31
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
32
36
  update_token_signet_base options
33
37
  self.universe_domain = options[:universe_domain] if options.key? :universe_domain
34
38
  self
35
39
  end
36
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
+
37
57
  def configure_connection options
38
58
  @connection_info =
39
59
  options[:connection_builder] || options[:default_connection]
@@ -61,6 +81,24 @@ module Signet
61
81
  info
62
82
  end
63
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
+
64
102
  def build_default_connection
65
103
  if !defined?(@connection_info)
66
104
  nil
@@ -75,20 +113,117 @@ module Signet
75
113
  retry_count = 0
76
114
 
77
115
  begin
78
- yield
116
+ yield.tap { |resp| log_response resp }
79
117
  rescue StandardError => e
80
- raise e if e.is_a?(Signet::AuthorizationError) || e.is_a?(Signet::ParseError)
118
+ if e.is_a?(Signet::AuthorizationError) || e.is_a?(Signet::ParseError)
119
+ log_auth_error e
120
+ raise e
121
+ end
81
122
 
82
123
  if retry_count < max_retry_count
124
+ log_transient_error e
83
125
  retry_count += 1
84
126
  sleep retry_count * 0.3
85
127
  retry
86
128
  else
129
+ log_retries_exhausted e
87
130
  msg = "Unexpected error: #{e.inspect}"
88
131
  raise Signet::AuthorizationError, msg
89
132
  end
90
133
  end
91
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
92
227
  end
93
228
  end
94
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 "Not implemented"
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 "Not implemented"
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 "Not implemented"
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] callback_uri
58
+ # @param [String] legacy_callback_uri
58
59
  # URL (either absolute or relative) of the auth callback.
59
- # Defaults to '/oauth2callback'
60
- def initialize client_id, scope, token_store, callback_uri = nil
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
@@ -85,6 +85,26 @@ module Google
85
85
  super options
86
86
  end
87
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
+
88
108
  # Revokes the credential
89
109
  def revoke! options = {}
90
110
  c = options[:connection] || Faraday.default_connection
@@ -114,6 +134,29 @@ module Google
114
134
  Google::Auth::ScopeUtil.normalize(scope)
115
135
  missing_scope.empty?
116
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
117
160
  end
118
161
  end
119
162
  end
@@ -16,6 +16,6 @@ module Google
16
16
  # Module Auth provides classes that provide Google-specific authorization
17
17
  # used to access Google APIs.
18
18
  module Auth
19
- VERSION = "1.9.2".freeze
19
+ VERSION = "1.14.0".freeze
20
20
  end
21
21
  end
@@ -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] callback_uri
96
+ # @param [String] legacy_callback_uri
97
97
  # URL (either absolute or relative) of the auth callback. Defaults
98
- # to '/oauth2callback'
99
- def initialize client_id, scope, token_store, callback_uri = nil
100
- super client_id, scope, token_store, callback_uri
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"
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: googleauth
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.2
4
+ version: 1.14.0
5
5
  platform: ruby
6
6
  authors:
7
- - Tim Emiola
8
- autorequire:
7
+ - Google LLC
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-01-25 00:00:00.000000000 Z
10
+ date: 2025-03-14 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: faraday
@@ -36,14 +35,28 @@ dependencies:
36
35
  requirements:
37
36
  - - "~>"
38
37
  - !ruby/object:Gem::Version
39
- version: '2.1'
38
+ version: '2.2'
40
39
  type: :runtime
41
40
  prerelease: false
42
41
  version_requirements: !ruby/object:Gem::Requirement
43
42
  requirements:
44
43
  - - "~>"
45
44
  - !ruby/object:Gem::Version
46
- version: '2.1'
45
+ version: '2.2'
46
+ - !ruby/object:Gem::Dependency
47
+ name: google-logging-utils
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '0.1'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '0.1'
47
60
  - !ruby/object:Gem::Dependency
48
61
  name: jwt
49
62
  requirement: !ruby/object:Gem::Requirement
@@ -121,7 +134,7 @@ dependencies:
121
134
  description: Implements simple authorization for accessing Google APIs, and provides
122
135
  support for Application Default Credentials.
123
136
  email:
124
- - temiola@google.com
137
+ - googleapis-packages@google.com
125
138
  executables: []
126
139
  extensions: []
127
140
  extra_rdoc_files: []
@@ -133,8 +146,10 @@ files:
133
146
  - README.md
134
147
  - SECURITY.md
135
148
  - lib/googleauth.rb
149
+ - lib/googleauth/api_key.rb
136
150
  - lib/googleauth/application_default.rb
137
151
  - lib/googleauth/base_client.rb
152
+ - lib/googleauth/bearer_token.rb
138
153
  - lib/googleauth/client_id.rb
139
154
  - lib/googleauth/compute_engine.rb
140
155
  - lib/googleauth/credentials.rb
@@ -152,10 +167,12 @@ files:
152
167
  - lib/googleauth/id_tokens/errors.rb
153
168
  - lib/googleauth/id_tokens/key_sources.rb
154
169
  - lib/googleauth/id_tokens/verifier.rb
170
+ - lib/googleauth/impersonated_service_account.rb
155
171
  - lib/googleauth/json_key_reader.rb
156
172
  - lib/googleauth/oauth2/sts_client.rb
157
173
  - lib/googleauth/scope_util.rb
158
174
  - lib/googleauth/service_account.rb
175
+ - lib/googleauth/service_account_jwt_header.rb
159
176
  - lib/googleauth/signet.rb
160
177
  - lib/googleauth/stores/file_token_store.rb
161
178
  - lib/googleauth/stores/redis_token_store.rb
@@ -171,7 +188,6 @@ metadata:
171
188
  changelog_uri: https://github.com/googleapis/google-auth-library-ruby/blob/main/CHANGELOG.md
172
189
  source_code_uri: https://github.com/googleapis/google-auth-library-ruby
173
190
  bug_tracker_uri: https://github.com/googleapis/google-auth-library-ruby/issues
174
- post_install_message:
175
191
  rdoc_options: []
176
192
  require_paths:
177
193
  - lib
@@ -179,15 +195,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
179
195
  requirements:
180
196
  - - ">="
181
197
  - !ruby/object:Gem::Version
182
- version: '2.7'
198
+ version: '3.0'
183
199
  required_rubygems_version: !ruby/object:Gem::Requirement
184
200
  requirements:
185
201
  - - ">="
186
202
  - !ruby/object:Gem::Version
187
203
  version: '0'
188
204
  requirements: []
189
- rubygems_version: 3.5.3
190
- signing_key:
205
+ rubygems_version: 3.6.5
191
206
  specification_version: 4
192
207
  summary: Google Auth Library for Ruby
193
208
  test_files: []