googleauth 1.8.0 → 1.13.1

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
 
@@ -25,6 +27,33 @@ module Signet
25
27
  class Client
26
28
  include Google::Auth::BaseClient
27
29
 
30
+ alias update_token_signet_base update_token!
31
+
32
+ def update_token! options = {}
33
+ options = deep_hash_normalize options
34
+ id_token_expires_at = expires_at_from_id_token options[:id_token]
35
+ options[:expires_at] = id_token_expires_at if id_token_expires_at
36
+ update_token_signet_base options
37
+ self.universe_domain = options[:universe_domain] if options.key? :universe_domain
38
+ self
39
+ end
40
+
41
+ alias update_signet_base update!
42
+ def update! options = {}
43
+ # Normalize all keys to symbols to allow indifferent access.
44
+ options = deep_hash_normalize options
45
+
46
+ # This `update!` method "overide" adds the `@logger`` update and
47
+ # the `universe_domain` update.
48
+ #
49
+ # The `universe_domain` is also updated in `update_token!` but is
50
+ # included here for completeness
51
+ self.universe_domain = options[:universe_domain] if options.key? :universe_domain
52
+ @logger = options[:logger] if options.key? :logger
53
+
54
+ update_signet_base options
55
+ end
56
+
28
57
  def configure_connection options
29
58
  @connection_info =
30
59
  options[:connection_builder] || options[:default_connection]
@@ -36,6 +65,9 @@ module Signet
36
65
  target_audience ? :id_token : :access_token
37
66
  end
38
67
 
68
+ # Set the universe domain
69
+ attr_accessor :universe_domain
70
+
39
71
  alias orig_fetch_access_token! fetch_access_token!
40
72
  def fetch_access_token! options = {}
41
73
  unless options[:connection]
@@ -49,6 +81,24 @@ module Signet
49
81
  info
50
82
  end
51
83
 
84
+ alias googleauth_orig_generate_access_token_request generate_access_token_request
85
+ def generate_access_token_request options = {}
86
+ parameters = googleauth_orig_generate_access_token_request options
87
+ logger&.info do
88
+ Google::Logging::Message.from(
89
+ message: "Requesting access token from #{parameters['grant_type']}",
90
+ "credentialsId" => object_id
91
+ )
92
+ end
93
+ logger&.debug do
94
+ Google::Logging::Message.from(
95
+ message: "Token fetch params: #{parameters}",
96
+ "credentialsId" => object_id
97
+ )
98
+ end
99
+ parameters
100
+ end
101
+
52
102
  def build_default_connection
53
103
  if !defined?(@connection_info)
54
104
  nil
@@ -63,20 +113,117 @@ module Signet
63
113
  retry_count = 0
64
114
 
65
115
  begin
66
- yield
116
+ yield.tap { |resp| log_response resp }
67
117
  rescue StandardError => e
68
- 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
69
122
 
70
123
  if retry_count < max_retry_count
124
+ log_transient_error e
71
125
  retry_count += 1
72
126
  sleep retry_count * 0.3
73
127
  retry
74
128
  else
129
+ log_retries_exhausted e
75
130
  msg = "Unexpected error: #{e.inspect}"
76
131
  raise Signet::AuthorizationError, msg
77
132
  end
78
133
  end
79
134
  end
135
+
136
+ # Creates a duplicate of these credentials
137
+ # without the Signet::OAuth2::Client-specific
138
+ # transient state (e.g. cached tokens)
139
+ #
140
+ # @param options [Hash] Overrides for the credentials parameters.
141
+ # @see Signet::OAuth2::Client#update!
142
+ def duplicate options = {}
143
+ options = deep_hash_normalize options
144
+
145
+ opts = {
146
+ authorization_uri: @authorization_uri,
147
+ token_credential_uri: @token_credential_uri,
148
+ client_id: @client_id,
149
+ client_secret: @client_secret,
150
+ scope: @scope,
151
+ target_audience: @target_audience,
152
+ redirect_uri: @redirect_uri,
153
+ username: @username,
154
+ password: @password,
155
+ issuer: @issuer,
156
+ person: @person,
157
+ sub: @sub,
158
+ audience: @audience,
159
+ signing_key: @signing_key,
160
+ extension_parameters: @extension_parameters,
161
+ additional_parameters: @additional_parameters,
162
+ access_type: @access_type,
163
+ universe_domain: @universe_domain,
164
+ logger: @logger
165
+ }.merge(options)
166
+
167
+ new_client = self.class.new opts
168
+
169
+ new_client.configure_connection options
170
+ end
171
+
172
+ private
173
+
174
+ def expires_at_from_id_token id_token
175
+ match = /^[\w=-]+\.([\w=-]+)\.[\w=-]+$/.match id_token.to_s
176
+ return unless match
177
+ json = JSON.parse Base64.urlsafe_decode64 match[1]
178
+ return unless json.key? "exp"
179
+ Time.at json["exp"].to_i
180
+ rescue StandardError
181
+ # Shouldn't happen unless we get a garbled ID token
182
+ nil
183
+ end
184
+
185
+ def log_response token_response
186
+ response_hash = JSON.parse token_response rescue {}
187
+ if response_hash["access_token"]
188
+ digest = Digest::SHA256.hexdigest response_hash["access_token"]
189
+ response_hash["access_token"] = "(sha256:#{digest})"
190
+ end
191
+ if response_hash["id_token"]
192
+ digest = Digest::SHA256.hexdigest response_hash["id_token"]
193
+ response_hash["id_token"] = "(sha256:#{digest})"
194
+ end
195
+ Google::Logging::Message.from(
196
+ message: "Received auth token response: #{response_hash}",
197
+ "credentialsId" => object_id
198
+ )
199
+ end
200
+
201
+ def log_auth_error err
202
+ logger&.info do
203
+ Google::Logging::Message.from(
204
+ message: "Auth error when fetching auth token: #{err}",
205
+ "credentialsId" => object_id
206
+ )
207
+ end
208
+ end
209
+
210
+ def log_transient_error err
211
+ logger&.info do
212
+ Google::Logging::Message.from(
213
+ message: "Transient error when fetching auth token: #{err}",
214
+ "credentialsId" => object_id
215
+ )
216
+ end
217
+ end
218
+
219
+ def log_retries_exhausted err
220
+ logger&.info do
221
+ Google::Logging::Message.from(
222
+ message: "Exhausted retries when fetching auth token: #{err}",
223
+ "credentialsId" => object_id
224
+ )
225
+ end
226
+ end
80
227
  end
81
228
  end
82
229
  end
@@ -29,7 +29,7 @@ module Google
29
29
  # @return [String]
30
30
  # The loaded token data.
31
31
  def load _id
32
- raise "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
@@ -50,7 +50,8 @@ module Google
50
50
  "client_secret" => ENV[CredentialsLoader::CLIENT_SECRET_VAR],
51
51
  "refresh_token" => ENV[CredentialsLoader::REFRESH_TOKEN_VAR],
52
52
  "project_id" => ENV[CredentialsLoader::PROJECT_ID_VAR],
53
- "quota_project_id" => nil
53
+ "quota_project_id" => nil,
54
+ "universe_domain" => nil
54
55
  }
55
56
  new(token_credential_uri: TOKEN_CRED_URI,
56
57
  client_id: user_creds["client_id"],
@@ -58,7 +59,8 @@ module Google
58
59
  refresh_token: user_creds["refresh_token"],
59
60
  project_id: user_creds["project_id"],
60
61
  quota_project_id: user_creds["quota_project_id"],
61
- scope: scope)
62
+ scope: scope,
63
+ universe_domain: user_creds["universe_domain"] || "googleapis.com")
62
64
  .configure_connection(options)
63
65
  end
64
66
 
@@ -83,6 +85,26 @@ module Google
83
85
  super options
84
86
  end
85
87
 
88
+ # Creates a duplicate of these credentials
89
+ # without the Signet::OAuth2::Client-specific
90
+ # transient state (e.g. cached tokens)
91
+ #
92
+ # @param options [Hash] Overrides for the credentials parameters.
93
+ # The following keys are recognized in addition to keys in the
94
+ # Signet::OAuth2::Client
95
+ # * `project_id` the project id to use during the authentication
96
+ # * `quota_project_id` the quota project id to use
97
+ # during the authentication
98
+ def duplicate options = {}
99
+ options = deep_hash_normalize options
100
+ super(
101
+ {
102
+ project_id: @project_id,
103
+ quota_project_id: @quota_project_id
104
+ }.merge(options)
105
+ )
106
+ end
107
+
86
108
  # Revokes the credential
87
109
  def revoke! options = {}
88
110
  c = options[:connection] || Faraday.default_connection
@@ -112,6 +134,29 @@ module Google
112
134
  Google::Auth::ScopeUtil.normalize(scope)
113
135
  missing_scope.empty?
114
136
  end
137
+
138
+ # Destructively updates these credentials
139
+ #
140
+ # This method is called by `Signet::OAuth2::Client`'s constructor
141
+ #
142
+ # @param options [Hash] Overrides for the credentials parameters.
143
+ # The following keys are recognized in addition to keys in the
144
+ # Signet::OAuth2::Client
145
+ # * `project_id` the project id to use during the authentication
146
+ # * `quota_project_id` the quota project id to use
147
+ # during the authentication
148
+ # @return [Google::Auth::UserRefreshCredentials]
149
+ def update! options = {}
150
+ # Normalize all keys to symbols to allow indifferent access.
151
+ options = deep_hash_normalize options
152
+
153
+ @project_id = options[:project_id] if options.key? :project_id
154
+ @quota_project_id = options[:quota_project_id] if options.key? :quota_project_id
155
+
156
+ super(options)
157
+
158
+ self
159
+ end
115
160
  end
116
161
  end
117
162
  end
@@ -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.8.0".freeze
19
+ VERSION = "1.13.1".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
@@ -192,13 +203,13 @@ module Google
192
203
  end
193
204
 
194
205
  def self.extract_callback_state request
195
- state = MultiJson.load(request[STATE_PARAM] || "{}")
206
+ state = MultiJson.load(request.params[STATE_PARAM] || "{}")
196
207
  redirect_uri = state[CURRENT_URI_KEY]
197
208
  callback_state = {
198
- AUTH_CODE_KEY => request[AUTH_CODE_KEY],
199
- ERROR_CODE_KEY => request[ERROR_CODE_KEY],
209
+ AUTH_CODE_KEY => request.params[AUTH_CODE_KEY],
210
+ ERROR_CODE_KEY => request.params[ERROR_CODE_KEY],
200
211
  SESSION_ID_KEY => state[SESSION_ID_KEY],
201
- SCOPE_KEY => request[SCOPE_KEY]
212
+ SCOPE_KEY => request.params[SCOPE_KEY]
202
213
  }
203
214
  [callback_state, redirect_uri]
204
215
  end
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.8.0
4
+ version: 1.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Emiola
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-09-08 00:00:00.000000000 Z
10
+ date: 2025-01-24 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: faraday
@@ -16,7 +15,7 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: 0.17.3
18
+ version: '1.0'
20
19
  - - "<"
21
20
  - !ruby/object:Gem::Version
22
21
  version: 3.a
@@ -26,10 +25,38 @@ dependencies:
26
25
  requirements:
27
26
  - - ">="
28
27
  - !ruby/object:Gem::Version
29
- version: 0.17.3
28
+ version: '1.0'
30
29
  - - "<"
31
30
  - !ruby/object:Gem::Version
32
31
  version: 3.a
32
+ - !ruby/object:Gem::Dependency
33
+ name: google-cloud-env
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '2.2'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
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'
33
60
  - !ruby/object:Gem::Dependency
34
61
  name: jwt
35
62
  requirement: !ruby/object:Gem::Requirement
@@ -138,6 +165,7 @@ files:
138
165
  - lib/googleauth/id_tokens/errors.rb
139
166
  - lib/googleauth/id_tokens/key_sources.rb
140
167
  - lib/googleauth/id_tokens/verifier.rb
168
+ - lib/googleauth/impersonated_service_account.rb
141
169
  - lib/googleauth/json_key_reader.rb
142
170
  - lib/googleauth/oauth2/sts_client.rb
143
171
  - lib/googleauth/scope_util.rb
@@ -157,7 +185,6 @@ metadata:
157
185
  changelog_uri: https://github.com/googleapis/google-auth-library-ruby/blob/main/CHANGELOG.md
158
186
  source_code_uri: https://github.com/googleapis/google-auth-library-ruby
159
187
  bug_tracker_uri: https://github.com/googleapis/google-auth-library-ruby/issues
160
- post_install_message:
161
188
  rdoc_options: []
162
189
  require_paths:
163
190
  - lib
@@ -165,15 +192,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
165
192
  requirements:
166
193
  - - ">="
167
194
  - !ruby/object:Gem::Version
168
- version: '2.6'
195
+ version: '2.7'
169
196
  required_rubygems_version: !ruby/object:Gem::Requirement
170
197
  requirements:
171
198
  - - ">="
172
199
  - !ruby/object:Gem::Version
173
200
  version: '0'
174
201
  requirements: []
175
- rubygems_version: 3.4.19
176
- signing_key:
202
+ rubygems_version: 3.6.2
177
203
  specification_version: 4
178
204
  summary: Google Auth Library for Ruby
179
205
  test_files: []