googleauth 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/Credentials.md +106 -0
  4. data/Errors.md +152 -0
  5. data/lib/googleauth/api_key.rb +9 -0
  6. data/lib/googleauth/application_default.rb +3 -1
  7. data/lib/googleauth/base_client.rb +5 -0
  8. data/lib/googleauth/bearer_token.rb +16 -2
  9. data/lib/googleauth/client_id.rb +9 -5
  10. data/lib/googleauth/compute_engine.rb +64 -18
  11. data/lib/googleauth/credentials.rb +23 -6
  12. data/lib/googleauth/credentials_loader.rb +9 -4
  13. data/lib/googleauth/default_credentials.rb +16 -4
  14. data/lib/googleauth/errors.rb +117 -0
  15. data/lib/googleauth/external_account/aws_credentials.rb +85 -18
  16. data/lib/googleauth/external_account/base_credentials.rb +31 -2
  17. data/lib/googleauth/external_account/external_account_utils.rb +15 -4
  18. data/lib/googleauth/external_account/identity_pool_credentials.rb +40 -15
  19. data/lib/googleauth/external_account/pluggable_credentials.rb +34 -19
  20. data/lib/googleauth/external_account.rb +30 -6
  21. data/lib/googleauth/iam.rb +19 -3
  22. data/lib/googleauth/id_tokens/errors.rb +13 -7
  23. data/lib/googleauth/id_tokens/key_sources.rb +13 -7
  24. data/lib/googleauth/id_tokens/verifier.rb +2 -3
  25. data/lib/googleauth/id_tokens.rb +4 -4
  26. data/lib/googleauth/impersonated_service_account.rb +64 -17
  27. data/lib/googleauth/json_key_reader.rb +11 -2
  28. data/lib/googleauth/oauth2/sts_client.rb +9 -4
  29. data/lib/googleauth/scope_util.rb +1 -1
  30. data/lib/googleauth/service_account.rb +12 -1
  31. data/lib/googleauth/service_account_jwt_header.rb +9 -2
  32. data/lib/googleauth/signet.rb +24 -6
  33. data/lib/googleauth/user_authorizer.rb +35 -7
  34. data/lib/googleauth/user_refresh.rb +25 -7
  35. data/lib/googleauth/version.rb +1 -1
  36. data/lib/googleauth/web_user_authorizer.rb +46 -9
  37. data/lib/googleauth.rb +1 -0
  38. metadata +8 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e2eef22c1062f2fd2216760e90a1af9ece1b99838bccea486a7b4ce43be9734
4
- data.tar.gz: 1e60ef4856de1e4f7a3d423f3953e5e39f86c7002216ad2d98ee46ec88aaaf25
3
+ metadata.gz: 34a8ff0141f7866d8f253bea4adbdd406c8aab873c7f18be6ac78ee981e16a39
4
+ data.tar.gz: 1191ce29114f13eb0d855ef46502015eb309cf694bd053171614b15d571e15f7
5
5
  SHA512:
6
- metadata.gz: d5a87f962d04eb59c9ae083a4d3c46fd54ef4d4c53f0571b3952f6d1a9d30e3af89fb2c50e04b118e301097850c9985c713968dc29c62c5d388d0d8a4c3d306d
7
- data.tar.gz: fae67434766ed2d4bb67e2c7ba2784a9397843110c28d96368d368b1dd30229a1ea83c5ee05e70a712446781e67ca84149fbaa72dd4239eb349d6943ff42b9b3
6
+ metadata.gz: 9286311e4de659c8820cdbdfb4ae48f1a3b0c519c66208a3c3f56a3a1079597b10306d6ab8aef05687df347262f26c75af63c4cbeac7570162829d926edf0317
7
+ data.tar.gz: 43954778b73cf95537a29a2317abe5b22eebfc3336e560657ec580542df9a7e9f42513968d27fc2b13621b0da0aa8fb8510ef59c9170bad611b0bd5b86f16888
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Release History
2
2
 
3
+ ### 1.15.0 (2025-08-25)
4
+
5
+ #### Features
6
+
7
+ * add typed errors to authentication library ([#533](https://github.com/googleapis/google-auth-library-ruby/issues/533))
8
+ * Support for JWT 3.x ([#542](https://github.com/googleapis/google-auth-library-ruby/issues/542))
9
+ #### Bug Fixes
10
+
11
+ * fix incorrect error and apply some code complexity refactoring ([#529](https://github.com/googleapis/google-auth-library-ruby/issues/529))
12
+ * support Pathname for cred loading ([#537](https://github.com/googleapis/google-auth-library-ruby/issues/537))
13
+ #### Documentation
14
+
15
+ * add summary documentation on credentials types and improve YARD comments
16
+ * add summary documentation on credentials types and improve YARD comments ([#530](https://github.com/googleapis/google-auth-library-ruby/issues/530))
17
+
3
18
  ### 1.14.0 (2025-03-14)
4
19
 
5
20
  #### Features
data/Credentials.md ADDED
@@ -0,0 +1,106 @@
1
+ # Introduction
2
+
3
+ The closest thing to a base credentials class is the `BaseClient` module.
4
+ It includes functionality common to most credentials, such as applying authentication tokens to request headers, managing token expiration and refresh, handling logging, and providing updater procs for API clients.
5
+
6
+ Many credentials classes inherit from `Signet::OAuth2::Client` (`lib/googleauth/signet.rb`) class which provides OAuth-based authentication.
7
+ The `Signet::OAuth2::Client` includes the `BaseClient` functionality.
8
+
9
+ Most credential types either inherit from `Signet::OAuth2::Client` or include the `BaseClient` module directly.
10
+
11
+ Notably, `Google::Auth::Credentials` (`lib/googleauth/credentials.rb`) is not a base type or a credentials type per se. It is a wrapper for other credential classes
12
+ that exposes common initialization functionality, such as creating credentials from environment variables, default paths, or application defaults. It is used and subclassed by Google's API client libraries.
13
+
14
+ # List of credentials types
15
+
16
+ ## Simple Authentication (non-OAuth)
17
+
18
+ **Google::Auth::APIKeyCredentials** - `lib/googleauth/api_key.rb`
19
+ - Includes `Google::Auth::BaseClient` module
20
+ - Implements Google API Key authentication
21
+ - API Keys are text strings that don't have an associated JSON file
22
+ - API Keys provide project information but don't reference an IAM principal
23
+ - They do not expire and cannot be refreshed
24
+ - Can be loaded from the `GOOGLE_API_KEY` environment variable
25
+
26
+ 2. **Google::Auth::BearerTokenCredentials** - `lib/googleauth/bearer_token.rb`
27
+ - Includes `Google::Auth::BaseClient` module
28
+ - Implements Bearer Token authentication
29
+ - Bearer tokens are strings representing an authorization grant
30
+ - Can be OAuth2 tokens, JWTs, ID tokens, or any token sent as a `Bearer` in an `Authorization` header
31
+ - Used when the end-user is managing the token separately (e.g., with another service)
32
+ - Token lifetime tracking and refresh are outside this class's scope
33
+ - No JSON representation for this type of credentials
34
+
35
+ ## GCP-Specialized authentication
36
+
37
+ 3. **Google::Auth::GCECredentials < Signet::OAuth2::Client** - `lib/googleauth/compute_engine.rb`
38
+ - For obtaining authentication tokens from GCE metadata server
39
+ - Used automatically when code is running on Google Compute Engine
40
+ - Fetches tokens from the metadata server with no additional configuration needed
41
+
42
+ 4. **Google::Auth::IAMCredentials < Signet::OAuth2::Client** - `lib/googleauth/iam.rb`
43
+ - For IAM-based authentication (e.g. service-to-service)
44
+ - Implements authentication-as-a-service for systems already authenticated
45
+ - Exchanges existing credentials for a short-lived access token
46
+
47
+ ## Service Account Authentication
48
+
49
+ 5. **Google::Auth::ServiceAccountCredentials < Signet::OAuth2::Client** - `lib/googleauth/service_account.rb`
50
+ - Authenticates requests using Service Account credentials via an OAuth access token
51
+ - Created from JSON key file downloaded from Google Cloud Console
52
+ - Supports both OAuth access tokens and self-signed JWT authentication
53
+ - Can specify scopes for access token requests
54
+
55
+ 6. **Google::Auth::ServiceAccountJwtHeaderCredentials** - `lib/googleauth/service_account_jwt_header.rb`
56
+ - Authenticates using Service Account credentials with JWT headers
57
+ - Typically used via `ServiceAccountCredentials` and not by itself
58
+ - Creates JWT directly for making authenticated calls
59
+ - Does not require a round trip to the authorization server
60
+ - Doesn't support OAuth scopes - uses audience (target API) instead
61
+
62
+ 7. **Google::Auth::ImpersonatedServiceAccountCredentials < Signet::OAuth2::Client** - `lib/googleauth/impersonated_service_account.rb`
63
+ - For service account impersonation
64
+ - Allows a GCP principal identified by a set of source credentials to impersonate a service account
65
+ - Useful for delegation of authority and managing permissions across service accounts
66
+ - Source credentials must have the Service Account Token Creator role on the target
67
+
68
+ ## User Authentication
69
+
70
+ 8. **Google::Auth::UserRefreshCredentials < Signet::OAuth2::Client** - `lib/googleauth/user_refresh.rb`
71
+ - For user refresh token authentication (from 3-legged OAuth flow)
72
+ - Authenticates on behalf of a user who has authorized the application
73
+ - Handles token refresh when original access token expires
74
+ - Typically obtained through web or installed application flow
75
+
76
+ `Google::Auth::UserAuthorizer` (`lib/googleauth/user_authorizer.rb`) and `Google::Auth::WebUserAuthorizer` (`lib/googleauth/web_user_authorizer.rb`)
77
+ are used to facilitate user authentication. The `UserAuthorizer` handles interactive 3-Legged-OAuth2 (3LO) user consent authorization for command-line applications.
78
+ The `WebUserAuthorizer` is a variation of UserAuthorizer adapted for Rack-based web applications that manages OAuth state and provides callback handling.
79
+
80
+ ## External Account Authentication
81
+ `Google::Auth::ExternalAccount::Credentials` (`lib/googleauth/external_account.rb`) is not a credentials type, it is a module
82
+ that procides an entry point for External Account credentials. It also serves as a factory that creates appropriate credential
83
+ types based on credential source (similar to `Google::Auth::get_application_default`).
84
+ It is included in all External Account credentials types, and it itself includes `Google::Auth::BaseClient` module so all External
85
+ Account credentials types include `Google::Auth::BaseClient`.
86
+
87
+ 9. **Google::Auth::ExternalAccount::AwsCredentials** - `lib/googleauth/external_account/aws_credentials.rb`
88
+ - Includes `Google::Auth::BaseClient` module
89
+ - Includes `ExternalAccount::BaseCredentials` module
90
+ - Uses AWS credentials to authenticate to Google Cloud
91
+ - Exchanges temporary AWS credentials for Google access tokens
92
+ - Used for workloads running on AWS that need to access Google Cloud
93
+
94
+ 10. **Google::Auth::ExternalAccount::IdentityPoolCredentials** - `lib/googleauth/external_account/identity_pool_credentials.rb`
95
+ - Includes `Google::Auth::BaseClient` module
96
+ - Includes `ExternalAccount::BaseCredentials` module
97
+ - Authenticates using external identity pool
98
+ - Exchanges external identity tokens for Google access tokens
99
+ - Supports file-based and URL-based credential sources
100
+
101
+ 11. **Google::Auth::ExternalAccount::PluggableCredentials** - `lib/googleauth/external_account/pluggable_credentials.rb`
102
+ - Includes `Google::Auth::BaseClient` module
103
+ - Includes `ExternalAccount::BaseCredentials` module
104
+ - Supports executable-based credential sources
105
+ - Executes external programs to retrieve credentials
106
+ - Allows for custom authentication mechanisms via external executables
data/Errors.md ADDED
@@ -0,0 +1,152 @@
1
+ # Error Handling in Google Auth Library for Ruby
2
+
3
+ ## Overview
4
+
5
+ The Google Auth Library for Ruby provides a structured approach to error handling. This document explains the error hierarchy, how to access detailed error information, and provides examples of handling errors effectively.
6
+
7
+ ## Error Hierarchy
8
+
9
+ The Google Auth Library has two main error hierarchies: the core authentication errors and the specialized ID token flow errors.
10
+
11
+ ### Core Authentication Errors
12
+
13
+ These errors are used throughout the main library for general authentication and credential operations:
14
+
15
+ ```
16
+ Google::Auth::Error (module)
17
+ ├── Google::Auth::InitializationError (class)
18
+ └── Google::Auth::DetailedError (module)
19
+ ├── Google::Auth::CredentialsError (class)
20
+ ├── Google::Auth::AuthorizationError (class)
21
+ ├── Google::Auth::UnexpectedStatusError (class)
22
+ └── Google::Auth::ParseError (class)
23
+ ```
24
+
25
+ ### ID Token Errors
26
+
27
+ These specialized errors are used specifically for ID token flow. They also include the `Google::Auth::Error` module, allowing them to be caught with the same error handling as the core authentication errors:
28
+
29
+ ```
30
+ Google::Auth::Error (module)
31
+ ├── Google::Auth::IDTokens::KeySourceError (class)
32
+ └── Google::Auth::IDTokens::VerificationError (class)
33
+ ├── ExpiredTokenError (class)
34
+ ├── SignatureError (class)
35
+ ├── IssuerMismatchError (class)
36
+ ├── AudienceMismatchError (class)
37
+ └── AuthorizedPartyMismatchError (class)
38
+ ```
39
+
40
+ ### Error Module Types
41
+
42
+ - **`Google::Auth::Error`**: Base module that all Google Auth errors include. Use this to catch any error from the library.
43
+
44
+ - **`Google::Auth::DetailedError`**: Extends `Error` to include detailed information about the credential that caused the error, including the credential type and principal.
45
+
46
+ ## Core Authentication Error Classes
47
+
48
+ - **`InitializationError`**: Raised during credential initialization when required parameters are missing or invalid.
49
+
50
+ - **`CredentialsError`**: Generic error raised during authentication flows.
51
+
52
+ - **`AuthorizationError`**: Raised when a remote server refuses to authorize the client. Inherits from `Signet::AuthorizationError`. Is being raised where `Signet::AuthorizationError` was raised previously.
53
+
54
+ - **`UnexpectedStatusError`**: Raised when a server returns an unexpected HTTP status code. Inherits from `Signet::UnexpectedStatusError`. Is being raised where `Signet::UnexpectedStatusError` was raised previously.
55
+
56
+ - **`ParseError`**: Raised when the client fails to parse a value from a response. Inherits from `Signet::ParseError`. Is being raised where `Signet::ParseError` was raised previously.
57
+
58
+ ## Detailed Error Information
59
+
60
+ Errors that include the `DetailedError` module provide additional context about what went wrong:
61
+
62
+ - **`credential_type_name`**: The class name of the credential that raised the error (e.g., `"Google::Auth::ServiceAccountCredentials"`)
63
+
64
+ - **`principal`**: The identity associated with the credentials (e.g., an email address for service accounts, `:api_key` for API key credentials)
65
+
66
+ ### Example: Catching and Handling Core Errors
67
+
68
+ ```ruby
69
+ begin
70
+ credentials = Google::Auth::ServiceAccountCredentials.make_creds(
71
+ json_key_io: File.open("your-key.json")
72
+ )
73
+ # Use credentials...
74
+ rescue Google::Auth::InitializationError => e
75
+ puts "Failed to initialize credentials: #{e.message}"
76
+ # e.g., Missing required fields in the service account key file
77
+ rescue Google::Auth::DetailedError => e
78
+ puts "Authorization failed: #{e.message}"
79
+ puts "Credential type: #{e.credential_type_name}"
80
+ puts "Principal: #{e.principal}"
81
+ # e.g., Invalid or revoked service account
82
+ rescue Google::Auth::Error => e
83
+ puts "Unknown Google Auth error: #{e.message}"
84
+ end
85
+ ```
86
+
87
+ ## Backwards compatibility
88
+
89
+ Some classes in the Google Auth Library raise standard Ruby `ArgumentError` and `TypeError`. These errors are preserved for backward compatibility, however the new code will raise `Google::Auth::InitializationError` instead.
90
+
91
+ ## ID Token Verification
92
+
93
+ The Google Auth Library includes functionality for verifying ID tokens through the `Google::Auth::IDTokens` namespace. These operations have their own specialized error classes that also include the `Google::Auth::Error` module, allowing them to be caught with the same error handling as other errors in the library.
94
+
95
+ ### ID Token Error Classes
96
+
97
+ - **`KeySourceError`**: Raised when the library fails to obtain the keys needed to verify a token, typically from a JWKS (JSON Web Key Set) endpoint.
98
+
99
+ - **`VerificationError`**: Base class for all errors related to token verification failures.
100
+
101
+ - **`ExpiredTokenError`**: Raised when a token has expired according to its expiration time claim (`exp`).
102
+
103
+ - **`SignatureError`**: Raised when a token's signature cannot be verified, indicating it might be tampered with or corrupted.
104
+
105
+ - **`IssuerMismatchError`**: Raised when a token's issuer (`iss` claim) doesn't match the expected issuer.
106
+
107
+ - **`AudienceMismatchError`**: Raised when a token's audience (`aud` claim) doesn't match the expected audience.
108
+
109
+ - **`AuthorizedPartyMismatchError`**: Raised when a token's authorized party (`azp` claim) doesn't match the expected client ID.
110
+
111
+ ### Example: Handling ID Token Verification Errors
112
+
113
+ ```ruby
114
+ require "googleauth/id_tokens"
115
+
116
+ begin
117
+ # Verify the provided ID token
118
+ payload = Google::Auth::IDTokens.verify_oidc(
119
+ id_token,
120
+ audience: "expected-audience-12345.apps.googleusercontent.com"
121
+ )
122
+
123
+ # Use the verified token payload
124
+ user_email = payload["email"]
125
+
126
+ rescue Google::Auth::IDTokens::ExpiredTokenError => e
127
+ puts "The token has expired. Please obtain a new one."
128
+
129
+ rescue Google::Auth::IDTokens::SignatureError => e
130
+ puts "Invalid token signature."
131
+
132
+ rescue Google::Auth::IDTokens::IssuerMismatchError => e
133
+ puts "Invalid token issuer."
134
+
135
+ rescue Google::Auth::IDTokens::AudienceMismatchError => e
136
+ puts "This token is not intended for this application (invalid audience)."
137
+
138
+ rescue Google::Auth::IDTokens::AuthorizedPartyMismatchError => e
139
+ puts "Invalid token authorized party."
140
+
141
+ rescue Google::Auth::IDTokens::VerificationError => e
142
+ puts "Token verification failed: #{e.message}"
143
+ # Generic verification error handling
144
+
145
+ rescue Google::Auth::IDTokens::KeySourceError => e
146
+ puts "Unable to retrieve verification keys: #{e.message}"
147
+
148
+ rescue Google::Auth::Error => e
149
+ puts "Unknown Google Auth error: #{e.message}"
150
+ # This will catch any Google Auth error
151
+ end
152
+ ```
@@ -85,6 +85,7 @@ module Google
85
85
  # @option options [String] :universe_domain
86
86
  # The universe domain of the universe this API key
87
87
  # belongs to (defaults to googleapis.com)
88
+ # @raise [ArgumentError] If the API key is nil or empty
88
89
  def initialize options = {}
89
90
  raise ArgumentError, "API key must be provided" if options[:api_key].nil? || options[:api_key].empty?
90
91
  @api_key = options[:api_key]
@@ -139,6 +140,14 @@ module Google
139
140
  a_hash
140
141
  end
141
142
 
143
+ # For credentials that are initialized with a token without a principal,
144
+ # the type of that token should be returned as a principal instead
145
+ # @private
146
+ # @return [Symbol] the token type in lieu of the principal
147
+ def principal
148
+ token_type
149
+ end
150
+
142
151
  protected
143
152
 
144
153
  # The token type should be :api_key
@@ -14,6 +14,7 @@
14
14
 
15
15
  require "googleauth/compute_engine"
16
16
  require "googleauth/default_credentials"
17
+ require "googleauth/errors"
17
18
 
18
19
  module Google
19
20
  # Module Auth provides classes that provide Google-specific authorization
@@ -50,12 +51,13 @@ module Google
50
51
  # connection to use for token refresh requests.
51
52
  # * `:connection` The connection to use to determine whether GCE
52
53
  # metadata credentials are available.
54
+ # @raise [Google::Auth::InitializationError] If the credentials cannot be found
53
55
  def get_application_default scope = nil, options = {}
54
56
  creds = DefaultCredentials.from_env(scope, options) ||
55
57
  DefaultCredentials.from_well_known_path(scope, options) ||
56
58
  DefaultCredentials.from_system_default_path(scope, options)
57
59
  return creds unless creds.nil?
58
- raise NOT_FOUND_ERROR unless GCECredentials.on_gce? options
60
+ raise InitializationError, NOT_FOUND_ERROR unless GCECredentials.on_gce? options
59
61
  GCECredentials.new options.merge(scope: scope)
60
62
  end
61
63
  end
@@ -78,6 +78,11 @@ module Google
78
78
  # The logger used to log operations on this client, such as token refresh.
79
79
  attr_accessor :logger
80
80
 
81
+ # @private
82
+ def principal
83
+ raise NoMethodError, "principal not implemented"
84
+ end
85
+
81
86
  private
82
87
 
83
88
  def token_type
@@ -13,6 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
  require "googleauth/base_client"
16
+ require "googleauth/errors"
16
17
 
17
18
  module Google
18
19
  module Auth
@@ -80,6 +81,7 @@ module Google
80
81
  # If `expires_at` is `nil`, it is treated as "token never expires".
81
82
  # @option options [String] :universe_domain The universe domain of the universe
82
83
  # this token is for (defaults to googleapis.com)
84
+ # @raise [ArgumentError] If the bearer token is nil or empty
83
85
  def initialize options = {}
84
86
  raise ArgumentError, "Bearer token must be provided" if options[:token].nil? || options[:token].empty?
85
87
  @token = options[:token]
@@ -119,6 +121,14 @@ module Google
119
121
  )
120
122
  end
121
123
 
124
+ # For credentials that are initialized with a token without a principal,
125
+ # the type of that token should be returned as a principal instead
126
+ # @private
127
+ # @return [Symbol] the token type in lieu of the principal
128
+ def principal
129
+ token_type
130
+ end
131
+
122
132
  protected
123
133
 
124
134
  ##
@@ -129,10 +139,14 @@ module Google
129
139
  #
130
140
  # @param [Hash] _options Options for fetching a new token (not used).
131
141
  # @return [nil] Always returns nil.
132
- # @raise [StandardError] If the token is expired.
142
+ # @raise [Google::Auth::CredentialsError] If the token is expired.
133
143
  def fetch_access_token! _options = {}
134
144
  if @expires_at && Time.now >= @expires_at
135
- raise "Bearer token has expired."
145
+ raise CredentialsError.with_details(
146
+ "Bearer token has expired.",
147
+ credential_type_name: self.class.name,
148
+ principal: principal
149
+ )
136
150
  end
137
151
 
138
152
  nil
@@ -14,6 +14,7 @@
14
14
 
15
15
  require "multi_json"
16
16
  require "googleauth/credentials_loader"
17
+ require "googleauth/errors"
17
18
 
18
19
  module Google
19
20
  module Auth
@@ -62,10 +63,11 @@ module Google
62
63
  # @note Direct instantiation is discouraged to avoid embedding IDs
63
64
  # and secrets in source. See {#from_file} to load from
64
65
  # `client_secrets.json` files.
66
+ # @raise [Google::Auth::InitializationError] If id or secret is nil
65
67
  #
66
68
  def initialize id, secret
67
- raise "Client id can not be nil" if id.nil?
68
- raise "Client secret can not be nil" if secret.nil?
69
+ raise InitializationError, "Client id can not be nil" if id.nil?
70
+ raise InitializationError, "Client secret can not be nil" if secret.nil?
69
71
  @id = id
70
72
  @secret = secret
71
73
  end
@@ -77,9 +79,10 @@ module Google
77
79
  # @param [String, File] file
78
80
  # Path of file to read from
79
81
  # @return [Google::Auth::ClientID]
82
+ # @raise [Google::Auth::InitializationError] If file is nil
80
83
  #
81
84
  def self.from_file file
82
- raise "File can not be nil." if file.nil?
85
+ raise InitializationError, "File can not be nil." if file.nil?
83
86
  File.open file.to_s do |f|
84
87
  json = f.read
85
88
  config = MultiJson.load json
@@ -94,11 +97,12 @@ module Google
94
97
  # @param [hash] config
95
98
  # Parsed contents of the JSON file
96
99
  # @return [Google::Auth::ClientID]
100
+ # @raise [Google::Auth::InitializationError] If config is nil or missing required elements
97
101
  #
98
102
  def self.from_hash config
99
- raise "Hash can not be nil." if config.nil?
103
+ raise InitializationError, "Hash can not be nil." if config.nil?
100
104
  raw_detail = config[INSTALLED_APP] || config[WEB_APP]
101
- raise MISSING_TOP_LEVEL_ELEMENT_ERROR if raw_detail.nil?
105
+ raise InitializationError, MISSING_TOP_LEVEL_ELEMENT_ERROR if raw_detail.nil?
102
106
  ClientId.new raw_detail[CLIENT_ID], raw_detail[CLIENT_SECRET]
103
107
  end
104
108
  end
@@ -13,6 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
  require "google-cloud-env"
16
+ require "googleauth/errors"
16
17
  require "googleauth/signet"
17
18
 
18
19
  module Google
@@ -126,31 +127,25 @@ module Google
126
127
 
127
128
  # Overrides the super class method to change how access tokens are
128
129
  # fetched.
130
+ #
131
+ # @param [Hash] _options Options for token fetch (not used)
132
+ # @return [Hash] The token data hash
133
+ # @raise [Google::Auth::UnexpectedStatusError] On unexpected HTTP status codes
134
+ # @raise [Google::Auth::AuthorizationError] If metadata server is unavailable or returns error
129
135
  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
136
+ query, entry = build_metadata_request_params
137
137
  begin
138
138
  log_fetch_query
139
139
  resp = Google::Cloud.env.lookup_metadata_response "instance", entry, query: query
140
140
  log_fetch_resp resp
141
- case resp.status
142
- when 200
143
- build_token_hash resp.body, resp.headers["content-type"], resp.retrieval_monotonic_time
144
- when 403, 500
145
- raise Signet::UnexpectedStatusError, "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
146
- when 404
147
- raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
148
- else
149
- raise Signet::AuthorizationError, "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
150
- end
141
+ handle_metadata_response resp
151
142
  rescue Google::Cloud::Env::MetadataServerNotResponding => e
152
143
  log_fetch_err e
153
- raise Signet::AuthorizationError, e.message
144
+ raise AuthorizationError.with_details(
145
+ e.message,
146
+ credential_type_name: self.class.name,
147
+ principal: principal
148
+ )
154
149
  end
155
150
  end
156
151
 
@@ -175,8 +170,47 @@ module Google
175
170
  self
176
171
  end
177
172
 
173
+ # Returns the principal identifier for GCE credentials
174
+ # @private
175
+ # @return [Symbol] :gce to represent Google Compute Engine identity
176
+ def principal
177
+ :gce_metadata
178
+ end
179
+
178
180
  private
179
181
 
182
+ # @private
183
+ # Builds query parameters and endpoint for metadata request
184
+ # @return [Array] The query parameters and endpoint path
185
+ def build_metadata_request_params
186
+ query, entry =
187
+ if token_type == :id_token
188
+ [{ "audience" => target_audience, "format" => "full" }, "service-accounts/default/identity"]
189
+ else
190
+ [{}, "service-accounts/default/token"]
191
+ end
192
+ query[:scopes] = Array(scope).join "," if scope
193
+ [query, entry]
194
+ end
195
+
196
+ # @private
197
+ # Handles the response from the metadata server
198
+ # @param [Google::Cloud::Env::MetadataResponse] resp The metadata server response
199
+ # @return [Hash] The token hash on success
200
+ # @raise [Google::Auth::UnexpectedStatusError, Google::Auth::AuthorizationError] On error
201
+ def handle_metadata_response resp
202
+ case resp.status
203
+ when 200
204
+ build_token_hash resp.body, resp.headers["content-type"], resp.retrieval_monotonic_time
205
+ when 403, 500
206
+ raise Signet::UnexpectedStatusError, "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
207
+ when 404
208
+ raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
209
+ else
210
+ raise Signet::AuthorizationError, "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
211
+ end
212
+ end
213
+
180
214
  def log_fetch_query
181
215
  if token_type == :id_token
182
216
  logger&.info do
@@ -213,6 +247,18 @@ module Google
213
247
  end
214
248
  end
215
249
 
250
+ # Constructs a token hash from the metadata server response
251
+ #
252
+ # @private
253
+ # @param [String] body The response body from the metadata server
254
+ # @param [String] content_type The content type of the response
255
+ # @param [Float] retrieval_time The monotonic time when the response was retrieved
256
+ #
257
+ # @return [Hash] A hash containing:
258
+ # - access_token/id_token: The actual token depending on what was requested
259
+ # - token_type: The type of token (usually "Bearer")
260
+ # - expires_in: Seconds until token expiration (adjusted for freshness)
261
+ # - universe_domain: The universe domain for the token (if not overridden)
216
262
  def build_token_hash body, content_type, retrieval_time
217
263
  hash =
218
264
  if ["text/html", "application/text"].include? content_type
@@ -14,9 +14,11 @@
14
14
 
15
15
  require "forwardable"
16
16
  require "json"
17
+ require "pathname"
17
18
  require "signet/oauth_2/client"
18
19
 
19
20
  require "googleauth/credentials_loader"
21
+ require "googleauth/errors"
20
22
 
21
23
  module Google
22
24
  module Auth
@@ -357,12 +359,13 @@ module Google
357
359
  # Creates a new Credentials instance with the provided auth credentials, and with the default
358
360
  # values configured on the class.
359
361
  #
360
- # @param [String, Hash, Signet::OAuth2::Client] source_creds
362
+ # @param [String, Pathname, Hash, Google::Auth::BaseClient] source_creds
361
363
  # The source of credentials. It can be provided as one of the following:
362
364
  #
363
- # * The path to a JSON keyfile (as a `String`)
365
+ # * The path to a JSON keyfile (as a `String` or a `Pathname`)
364
366
  # * The contents of a JSON keyfile (as a `Hash`)
365
- # * A `Signet::OAuth2::Client` credentials object
367
+ # * A `Google::Auth::BaseClient` credentials object, including but not limited to
368
+ # a `Signet::OAuth2::Client` object.
366
369
  # * Any credentials object that supports the methods this wrapper delegates to an inner client.
367
370
  #
368
371
  # If this parameter is an object (`Signet::OAuth2::Client` or other) it will be used as an inner client.
@@ -391,14 +394,20 @@ module Google
391
394
  # parameters of the `Signet::OAuth2::Client`, such as connection parameters,
392
395
  # timeouts, etc.
393
396
  #
397
+ # @raise [Google::Auth::InitializationError] If source_creds is nil
398
+ # @raise [ArgumentError] If both scope and target_audience are specified
399
+ #
394
400
  def initialize source_creds, options = {}
395
- raise "The source credentials passed to Google::Auth::Credentials.new were nil." if source_creds.nil?
401
+ if source_creds.nil?
402
+ raise InitializationError,
403
+ "The source credentials passed to Google::Auth::Credentials.new were nil."
404
+ end
396
405
 
397
406
  options = symbolize_hash_keys options
398
407
  @project_id = options[:project_id] || options[:project]
399
408
  @quota_project_id = options[:quota_project_id]
400
409
  case source_creds
401
- when String
410
+ when String, Pathname
402
411
  update_from_filepath source_creds, options
403
412
  when Hash
404
413
  update_from_hash source_creds, options
@@ -554,9 +563,12 @@ module Google
554
563
  protected
555
564
 
556
565
  # Verify that the keyfile argument is a file.
566
+ #
567
+ # @param [String] keyfile Path to the keyfile
568
+ # @raise [Google::Auth::InitializationError] If the keyfile does not exist
557
569
  def verify_keyfile_exists! keyfile
558
570
  exists = ::File.file? keyfile
559
- raise "The keyfile '#{keyfile}' is not a valid file." unless exists
571
+ raise InitializationError, "The keyfile '#{keyfile}' is not a valid file." unless exists
560
572
  end
561
573
 
562
574
  # Initializes the Signet client.
@@ -577,6 +589,11 @@ module Google
577
589
  hash.to_h.transform_keys(&:to_sym)
578
590
  end
579
591
 
592
+ # Updates client options with defaults from the credential class
593
+ #
594
+ # @param [Hash] options Options to update
595
+ # @return [Hash] Updated options hash
596
+ # @raise [ArgumentError] If both scope and target_audience are specified
580
597
  def update_client_options options
581
598
  options = options.dup
582
599