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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 888e57705b33e87158d060b7b1569e54e00000428de35f979e7ffc9456cbb7b3
4
- data.tar.gz: ac341b6c481df125091c1465b56642173591f5698baff6f4806b05216eca8802
3
+ metadata.gz: 6d8ca5b2b0c7f4ce54f7971d8de2f23f3ee0837d08d7d3c568c503308fcf82ab
4
+ data.tar.gz: d5f8b8fd2fcb4fef4240db58bf90f54a8bfd021c550a7bc9063c9087285f3921
5
5
  SHA512:
6
- metadata.gz: 0e9d2fd50c39bf83e86f9f31bbddb897d28ce908be1214849926c45ce7ad2fa0d8f48d70a01acc84c9fa49ddf1a3a3c0c5a87b9bdf1d3ae1b9430b739e709c67
7
- data.tar.gz: f92483b4971ecc9d48a0b3656a76c694974c6b60153d6ea5ff0f1dd6c544da51b924aedbfa71956db2c1dada98a6cdaa632bb6f38c663e384acc8ebb3af8d1d2
6
+ metadata.gz: 6a4de2b23f4dc0310a18568e0618c5d81fad54dfc8d57fe3c27b954c4bd21272fcc467c2c313f98f80fa127eab11ebe0d0fc55ceed7e6c5439764500e334df49
7
+ data.tar.gz: f4aff68138105ea19875bb7a51d4b6ced9ec2e7185ce725eca205ea1dc11beb9e6019c9dd89449866598ca6e4d3abb06b91f277f34e51ac7726d79a39fc40c67
data/CHANGELOG.md CHANGED
@@ -1,5 +1,93 @@
1
1
  # Release History
2
2
 
3
+ ### 1.13.1 (2025-01-24)
4
+
5
+ #### Bug Fixes
6
+
7
+ * Signet client subclasses no longer make the update! method private ([#516](https://github.com/googleapis/google-auth-library-ruby/issues/516))
8
+
9
+ ### 1.13.0 (2025-01-22)
10
+
11
+ #### Features
12
+
13
+ * create impersonated service credentials ([#499](https://github.com/googleapis/google-auth-library-ruby/issues/499))
14
+ #### Documentation
15
+
16
+ * Include note about validating externally-provided credentials ([#512](https://github.com/googleapis/google-auth-library-ruby/issues/512))
17
+
18
+ ### 1.12.2 (2024-12-19)
19
+
20
+ #### Bug Fixes
21
+
22
+ * GCECredentials lazily fetches from the metadata server to ensure a universe domain is known ([#509](https://github.com/googleapis/google-auth-library-ruby/issues/509))
23
+
24
+ ### 1.12.1 (2024-12-17)
25
+
26
+ #### Bug Fixes
27
+
28
+ * Restored previous behavior where the apply! method returns the auth header ([#506](https://github.com/googleapis/google-auth-library-ruby/issues/506))
29
+
30
+ ### 1.12.0 (2024-12-05)
31
+
32
+ #### Features
33
+
34
+ * provided opt-in debug logging ([#490](https://github.com/googleapis/google-auth-library-ruby/issues/490))
35
+
36
+ ### 1.11.2 (2024-10-23)
37
+
38
+ #### Bug Fixes
39
+
40
+ * Temporarily disable universe domain query from GCE metadata server ([#493](https://github.com/googleapis/google-auth-library-ruby/issues/493))
41
+ * Use updated metadata path for universe-domain ([#496](https://github.com/googleapis/google-auth-library-ruby/issues/496))
42
+
43
+ ### 1.11.1 (2024-10-04)
44
+
45
+ #### Bug Fixes
46
+
47
+ * Fixed parsing of expiration timestamp from ID tokens ([#492](https://github.com/googleapis/google-auth-library-ruby/issues/492))
48
+ * Use NoMethodError instead of NotImplementedError for unimplemented base class methods ([#487](https://github.com/googleapis/google-auth-library-ruby/issues/487))
49
+
50
+ ### 1.11.0 (2024-02-09)
51
+
52
+ #### Features
53
+
54
+ * Deprecate the positional argument for callback_uri, and introduce keyword argument instead ([#475](https://github.com/googleapis/google-auth-library-ruby/issues/475))
55
+
56
+ ### 1.10.0 (2024-02-08)
57
+
58
+ #### Features
59
+
60
+ * add PKCE to 3 Legged OAuth exchange ([#471](https://github.com/googleapis/google-auth-library-ruby/issues/471))
61
+ #### Bug Fixes
62
+
63
+ * Client library credentials provide correct self-signed JWT and external account behavior when loading from a file path or JSON data ([#474](https://github.com/googleapis/google-auth-library-ruby/issues/474))
64
+ * Prioritize universe domain specified in GCECredentials arguments over metadata-fetched value ([#472](https://github.com/googleapis/google-auth-library-ruby/issues/472))
65
+
66
+ ### 1.9.2 (2024-01-25)
67
+
68
+ #### Bug Fixes
69
+
70
+ * Prevent access tokens from being fetched at service account construction in the self-signed-jwt case ([#467](https://github.com/googleapis/google-auth-library-ruby/issues/467))
71
+
72
+ ### 1.9.1 (2023-12-12)
73
+
74
+ #### Bug Fixes
75
+
76
+ * update expires_in for cached metadata-retrieved tokens ([#464](https://github.com/googleapis/google-auth-library-ruby/issues/464))
77
+
78
+ ### 1.9.0 (2023-12-07)
79
+
80
+ #### Features
81
+
82
+ * Include universe_domain in credentials ([#460](https://github.com/googleapis/google-auth-library-ruby/issues/460))
83
+ * Use google-cloud-env for more robust Metadata Service access ([#459](https://github.com/googleapis/google-auth-library-ruby/issues/459))
84
+
85
+ ### 1.8.1 (2023-09-19)
86
+
87
+ #### Documentation
88
+
89
+ * improve ADC related error and warning messages ([#452](https://github.com/googleapis/google-auth-library-ruby/issues/452))
90
+
3
91
  ### 1.8.0 (2023-09-07)
4
92
 
5
93
  #### Features
data/README.md CHANGED
@@ -64,6 +64,15 @@ well as a web variant tailored toward Rack-based applications.
64
64
  The authorizers are intended for authorization use cases. For sign-on,
65
65
  see [Google Identity Platform](https://developers.google.com/identity/)
66
66
 
67
+ ## Important notes
68
+
69
+ If you accept a credential configuration (credential JSON/File/Stream) from an
70
+ external source for authentication to Google Cloud, you must validate it before
71
+ providing it to any Google API or library. Providing an unvalidated credential
72
+ configuration to Google APIs can compromise the security of your systems and data.
73
+ For more information, refer to [Validate credential configurations from external
74
+ sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
75
+
67
76
  ### Example (Web)
68
77
 
69
78
  ```ruby
@@ -97,6 +106,45 @@ get('/oauth2callback') do
97
106
  end
98
107
  ```
99
108
 
109
+ ### Example (Web with PKCE)
110
+
111
+ Proof Key for Code Exchange (PKCE) is an [RFC](https://www.rfc-editor.org/rfc/rfc7636) that aims to prevent malicious operating system processes from hijacking an OAUTH 2.0 exchange. PKCE mitigates the above vulnerability by including `code_challenge` and `code_challenge_method` parameters in the Authorization Request and a `code_verifier` parameter in the Access Token Request.
112
+
113
+ ```ruby
114
+ require 'googleauth'
115
+ require 'googleauth/web_user_authorizer'
116
+ require 'googleauth/stores/redis_token_store'
117
+ require 'redis'
118
+
119
+ client_id = Google::Auth::ClientId.from_file('/path/to/client_secrets.json')
120
+ scope = ['https://www.googleapis.com/auth/drive']
121
+ token_store = Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new)
122
+ authorizer = Google::Auth::WebUserAuthorizer.new(
123
+ client_id, scope, token_store, '/oauth2callback')
124
+
125
+
126
+ get('/authorize') do
127
+ # NOTE: Assumes the user is already authenticated to the app
128
+ user_id = request.session['user_id']
129
+ # User needs to take care of generating the code_verifier and storing it in
130
+ # the session.
131
+ request.session['code_verifier'] ||= Google::Auth::WebUserAuthorizer.generate_code_verifier
132
+ authorizer.code_verifier = request.session['code_verifier']
133
+ credentials = authorizer.get_credentials(user_id, request)
134
+ if credentials.nil?
135
+ redirect authorizer.get_authorization_url(login_hint: user_id, request: request)
136
+ end
137
+ # Credentials are valid, can call APIs
138
+ # ...
139
+ end
140
+
141
+ get('/oauth2callback') do
142
+ target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(
143
+ request)
144
+ redirect target_url
145
+ end
146
+ ```
147
+
100
148
  ### Example (Command Line) [Deprecated]
101
149
 
102
150
  The Google Auth OOB flow has been discontiued on January 31, 2023. The OOB flow is a legacy flow that is no longer considered secure. To continue using Google Auth, please migrate your applications to a more secure flow. For more information on how to do this, please refer to this [OOB Migration](https://developers.google.com/identity/protocols/oauth2/resources/oob-migration) guide.
@@ -20,9 +20,9 @@ module Google
20
20
  # used to access Google APIs.
21
21
  module Auth
22
22
  NOT_FOUND_ERROR = <<~ERROR_MESSAGE.freeze
23
- Could not load the default credentials. Browse to
24
- https://cloud.google.com/docs/authentication/provide-credentials-adc
25
- for more information
23
+ Your credentials were not found. To set up Application Default
24
+ Credentials for your environment, see
25
+ https://cloud.google.com/docs/authentication/external/set-up-adc
26
26
  ERROR_MESSAGE
27
27
 
28
28
  module_function
@@ -55,11 +55,7 @@ module Google
55
55
  DefaultCredentials.from_well_known_path(scope, options) ||
56
56
  DefaultCredentials.from_system_default_path(scope, options)
57
57
  return creds unless creds.nil?
58
- unless GCECredentials.on_gce? options
59
- # Clear cache of the result of GCECredentials.on_gce?
60
- GCECredentials.reset_cache
61
- raise NOT_FOUND_ERROR
62
- end
58
+ raise NOT_FOUND_ERROR unless GCECredentials.on_gce? options
63
59
  GCECredentials.new options.merge(scope: scope)
64
60
  end
65
61
  end
@@ -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 "google/logging/message"
16
+
15
17
  module Google
16
18
  # Module Auth provides classes that provide Google-specific authorization
17
19
  # used to access Google APIs.
@@ -29,7 +31,14 @@ module Google
29
31
  # fetch the access token there is currently not one, or if the client
30
32
  # has expired
31
33
  fetch_access_token! opts if needs_access_token?
32
- a_hash[AUTH_METADATA_KEY] = "Bearer #{send token_type}"
34
+ token = send token_type
35
+ a_hash[AUTH_METADATA_KEY] = "Bearer #{token}"
36
+ logger&.debug do
37
+ hash = Digest::SHA256.hexdigest token
38
+ Google::Logging::Message.from message: "Sending auth token. (sha256:#{hash})"
39
+ end
40
+
41
+ a_hash[AUTH_METADATA_KEY]
33
42
  end
34
43
 
35
44
  # Returns a clone of a_hash updated with the authentication token
@@ -63,17 +72,20 @@ module Google
63
72
  end
64
73
 
65
74
  def expires_within?
66
- raise NotImplementedError
75
+ raise NoMethodError, "expires_within? not implemented"
67
76
  end
68
77
 
78
+ # The logger used to log operations on this client, such as token refresh.
79
+ attr_accessor :logger
80
+
69
81
  private
70
82
 
71
83
  def token_type
72
- raise NotImplementedError
84
+ raise NoMethodError, "token_type not implemented"
73
85
  end
74
86
 
75
87
  def fetch_access_token!
76
- raise NotImplementedError
88
+ raise NoMethodError, "fetch_access_token! not implemented"
77
89
  end
78
90
  end
79
91
  end
@@ -64,7 +64,6 @@ module Google
64
64
  # `client_secrets.json` files.
65
65
  #
66
66
  def initialize id, secret
67
- CredentialsLoader.warn_if_cloud_sdk_credentials id
68
67
  raise "Client id can not be nil" if id.nil?
69
68
  raise "Client secret can not be nil" if secret.nil?
70
69
  @id = id
@@ -12,7 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- require "faraday"
15
+ require "google-cloud-env"
16
16
  require "googleauth/signet"
17
17
 
18
18
  module Google
@@ -33,94 +33,230 @@ module Google
33
33
  # Extends Signet::OAuth2::Client so that the auth token is obtained from
34
34
  # the GCE metadata server.
35
35
  class GCECredentials < Signet::OAuth2::Client
36
- # The IP Address is used in the URIs to speed up failures on non-GCE
37
- # systems.
36
+ # @private Unused and deprecated but retained to prevent breaking changes
38
37
  DEFAULT_METADATA_HOST = "169.254.169.254".freeze
39
38
 
40
- # @private Unused and deprecated
39
+ # @private Unused and deprecated but retained to prevent breaking changes
41
40
  COMPUTE_AUTH_TOKEN_URI =
42
41
  "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
43
- # @private Unused and deprecated
42
+ # @private Unused and deprecated but retained to prevent breaking changes
44
43
  COMPUTE_ID_TOKEN_URI =
45
44
  "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity".freeze
46
- # @private Unused and deprecated
45
+ # @private Unused and deprecated but retained to prevent breaking changes
47
46
  COMPUTE_CHECK_URI = "http://169.254.169.254".freeze
48
47
 
49
- @on_gce_cache = {}
50
-
51
48
  class << self
49
+ # @private Unused and deprecated
52
50
  def metadata_host
53
51
  ENV.fetch "GCE_METADATA_HOST", DEFAULT_METADATA_HOST
54
52
  end
55
53
 
54
+ # @private Unused and deprecated
56
55
  def compute_check_uri
57
56
  "http://#{metadata_host}".freeze
58
57
  end
59
58
 
59
+ # @private Unused and deprecated
60
60
  def compute_auth_token_uri
61
61
  "#{compute_check_uri}/computeMetadata/v1/instance/service-accounts/default/token".freeze
62
62
  end
63
63
 
64
+ # @private Unused and deprecated
64
65
  def compute_id_token_uri
65
66
  "#{compute_check_uri}/computeMetadata/v1/instance/service-accounts/default/identity".freeze
66
67
  end
67
68
 
68
69
  # Detect if this appear to be a GCE instance, by checking if metadata
69
70
  # is available.
70
- def on_gce? options = {}, reload = false # rubocop:disable Style/OptionalBooleanParameter
71
- # We can follow OptionalBooleanParameter here because it's a public interface, we can't change it.
72
- @on_gce_cache.delete options if reload
73
- @on_gce_cache.fetch options do
74
- @on_gce_cache[options] = begin
75
- # TODO: This should use google-cloud-env instead.
76
- c = options[:connection] || Faraday.default_connection
77
- headers = { "Metadata-Flavor" => "Google" }
78
- resp = c.get compute_check_uri, nil, headers do |req|
79
- req.options.timeout = 1.0
80
- req.options.open_timeout = 0.1
81
- end
82
- return false unless resp.status == 200
83
- resp.headers["Metadata-Flavor"] == "Google"
84
- rescue Faraday::TimeoutError, Faraday::ConnectionFailed
85
- false
86
- end
87
- end
71
+ # The parameters are deprecated and unused.
72
+ def on_gce? _options = {}, _reload = false # rubocop:disable Style/OptionalBooleanParameter
73
+ Google::Cloud.env.metadata?
88
74
  end
89
75
 
90
76
  def reset_cache
91
- @on_gce_cache.clear
77
+ Google::Cloud.env.compute_metadata.reset_existence!
78
+ Google::Cloud.env.compute_metadata.cache.expire_all!
92
79
  end
93
80
  alias unmemoize_all reset_cache
94
81
  end
95
82
 
83
+ # @private Temporary; remove when universe domain metadata endpoint is stable (see b/349488459).
84
+ attr_accessor :disable_universe_domain_check
85
+
86
+ # Construct a GCECredentials
87
+ def initialize options = {}
88
+ # Override the constructor to remember whether the universe domain was
89
+ # overridden by a constructor argument.
90
+ @universe_domain_overridden = options["universe_domain"] || options[:universe_domain] ? true : false
91
+ # TODO: Remove when universe domain metadata endpoint is stable (see b/349488459).
92
+ @disable_universe_domain_check = true
93
+ super options
94
+ end
95
+
96
+ # Creates a duplicate of these credentials
97
+ # without the Signet::OAuth2::Client-specific
98
+ # transient state (e.g. cached tokens)
99
+ #
100
+ # @param options [Hash] Overrides for the credentials parameters.
101
+ # The following keys are recognized in addition to keys in the
102
+ # Signet::OAuth2::Client
103
+ # * `:universe_domain_overridden` Whether the universe domain was
104
+ # overriden during credentials creation
105
+ def duplicate options = {}
106
+ options = deep_hash_normalize options
107
+ super(
108
+ {
109
+ universe_domain_overridden: @universe_domain_overridden
110
+ }.merge(options)
111
+ )
112
+ end
113
+
114
+ # @private
115
+ # Overrides universe_domain getter to fetch lazily if it hasn't been
116
+ # fetched yet. This is necessary specifically for Compute Engine because
117
+ # the universe comes from the metadata service, and isn't known
118
+ # immediately on credential construction. All other credential types read
119
+ # the universe from their json key or other immediate input.
120
+ def universe_domain
121
+ value = super
122
+ return value unless value.nil?
123
+ fetch_access_token!
124
+ super
125
+ end
126
+
96
127
  # Overrides the super class method to change how access tokens are
97
128
  # fetched.
98
- def fetch_access_token options = {}
99
- c = options[:connection] || Faraday.default_connection
100
- retry_with_error do
101
- uri = target_audience ? GCECredentials.compute_id_token_uri : GCECredentials.compute_auth_token_uri
102
- query = target_audience ? { "audience" => target_audience, "format" => "full" } : {}
103
- query[:scopes] = Array(scope).join "," if scope
104
- resp = c.get uri, query, "Metadata-Flavor" => "Google"
129
+ def fetch_access_token _options = {}
130
+ query, entry =
131
+ if token_type == :id_token
132
+ [{ "audience" => target_audience, "format" => "full" }, "service-accounts/default/identity"]
133
+ else
134
+ [{}, "service-accounts/default/token"]
135
+ end
136
+ query[:scopes] = Array(scope).join "," if scope
137
+ begin
138
+ log_fetch_query
139
+ resp = Google::Cloud.env.lookup_metadata_response "instance", entry, query: query
140
+ log_fetch_resp resp
105
141
  case resp.status
106
142
  when 200
107
- content_type = resp.headers["content-type"]
108
- if ["text/html", "application/text"].include? content_type
109
- { (target_audience ? "id_token" : "access_token") => resp.body }
110
- else
111
- Signet::OAuth2.parse_credentials resp.body, content_type
112
- end
143
+ build_token_hash resp.body, resp.headers["content-type"], resp.retrieval_monotonic_time
113
144
  when 403, 500
114
- msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
115
- raise Signet::UnexpectedStatusError, msg
145
+ raise Signet::UnexpectedStatusError, "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
116
146
  when 404
117
147
  raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
118
148
  else
119
- msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
120
- raise Signet::AuthorizationError, msg
149
+ raise Signet::AuthorizationError, "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
121
150
  end
151
+ rescue Google::Cloud::Env::MetadataServerNotResponding => e
152
+ log_fetch_err e
153
+ raise Signet::AuthorizationError, e.message
122
154
  end
123
155
  end
156
+
157
+ # Destructively updates these credentials.
158
+ #
159
+ # This method is called by `Signet::OAuth2::Client`'s constructor
160
+ #
161
+ # @param options [Hash] Overrides for the credentials parameters.
162
+ # The following keys are recognized in addition to keys in the
163
+ # Signet::OAuth2::Client
164
+ # * `:universe_domain_overridden` Whether the universe domain was
165
+ # overriden during credentials creation
166
+ # @return [Google::Auth::GCECredentials]
167
+ def update! options = {}
168
+ # Normalize all keys to symbols to allow indifferent access.
169
+ options = deep_hash_normalize options
170
+
171
+ @universe_domain_overridden = options[:universe_domain_overridden] if options.key? :universe_domain_overridden
172
+
173
+ super(options)
174
+
175
+ self
176
+ end
177
+
178
+ private
179
+
180
+ def log_fetch_query
181
+ if token_type == :id_token
182
+ logger&.info do
183
+ Google::Logging::Message.from(
184
+ message: "Requesting id token from MDS with aud=#{target_audience}",
185
+ "credentialsId" => object_id
186
+ )
187
+ end
188
+ else
189
+ logger&.info do
190
+ Google::Logging::Message.from(
191
+ message: "Requesting access token from MDS",
192
+ "credentialsId" => object_id
193
+ )
194
+ end
195
+ end
196
+ end
197
+
198
+ def log_fetch_resp resp
199
+ logger&.info do
200
+ Google::Logging::Message.from(
201
+ message: "Received #{resp.status} from MDS",
202
+ "credentialsId" => object_id
203
+ )
204
+ end
205
+ end
206
+
207
+ def log_fetch_err _err
208
+ logger&.info do
209
+ Google::Logging::Message.from(
210
+ message: "MDS did not respond to token request",
211
+ "credentialsId" => object_id
212
+ )
213
+ end
214
+ end
215
+
216
+ def build_token_hash body, content_type, retrieval_time
217
+ hash =
218
+ if ["text/html", "application/text"].include? content_type
219
+ parse_encoded_token body
220
+ else
221
+ Signet::OAuth2.parse_credentials body, content_type
222
+ end
223
+ add_universe_domain_to hash
224
+ adjust_for_stale_expires_in hash, retrieval_time
225
+ hash
226
+ end
227
+
228
+ def parse_encoded_token body
229
+ hash = { token_type.to_s => body }
230
+ if token_type == :id_token
231
+ expires_at = expires_at_from_id_token body
232
+ hash["expires_at"] = expires_at if expires_at
233
+ end
234
+ hash
235
+ end
236
+
237
+ def add_universe_domain_to hash
238
+ return if @universe_domain_overridden
239
+ universe_domain =
240
+ if disable_universe_domain_check
241
+ # TODO: Remove when universe domain metadata endpoint is stable (see b/349488459).
242
+ "googleapis.com"
243
+ else
244
+ Google::Cloud.env.lookup_metadata "universe", "universe-domain"
245
+ end
246
+ universe_domain = "googleapis.com" if !universe_domain || universe_domain.empty?
247
+ hash["universe_domain"] = universe_domain.strip
248
+ end
249
+
250
+ # The response might have been cached, which means expires_in might be
251
+ # stale. Update it based on the time since the data was retrieved.
252
+ # We also ensure expires_in is conservative; subtracting at least 1
253
+ # second to offset any skew from metadata server latency.
254
+ def adjust_for_stale_expires_in hash, retrieval_time
255
+ return unless hash["expires_in"].is_a? Numeric
256
+ offset = 1 + (Process.clock_gettime(Process::CLOCK_MONOTONIC) - retrieval_time).round
257
+ hash["expires_in"] -= offset if offset.positive?
258
+ hash["expires_in"] = 0 if hash["expires_in"].negative?
259
+ end
124
260
  end
125
261
  end
126
262
  end