Skip to content

Commit 3ffc931

Browse files
authored
Improve error messages when failing to get access token (#263)
* Improve error messages when failing to get refresh token * Add warning when debug logging is enabled * Redact refresh token * Expose end of redacted tokens, not beginning
1 parent e968cee commit 3ffc931

File tree

4 files changed

+62
-11
lines changed

4 files changed

+62
-11
lines changed

lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ class FirebaseAppDistributionAction < Action
1818
def self.run(params)
1919
params.values # to validate all inputs before looking for the ipa/apk/aab
2020

21+
if params[:debug]
22+
UI.important("Warning: Debug logging enabled. Output may include sensitive information.")
23+
end
24+
2125
app_id = app_id_from_params(params)
2226
app_name = app_name_from_app_id(app_id)
2327
platform = lane_platform || platform_from_app_id(app_id)

lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_auth_client.rb

+34-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ module Fastlane
44
module Auth
55
module FirebaseAppDistributionAuthClient
66
TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token"
7+
REDACTION_EXPOSED_LENGTH = 5
8+
REDACTION_CHARACTER = "X"
79

810
# Returns the auth token for any of the auth methods (Firebase CLI token,
911
# Google service account, firebase-tools). To ensure that a specific
@@ -73,8 +75,14 @@ def firebase_token(refresh_token, debug)
7375
client.fetch_access_token!
7476
client.access_token
7577
rescue Signet::AuthorizationError => error
76-
log_authorization_error_details(error) if debug
77-
UI.user_error!(ErrorMessage::REFRESH_TOKEN_ERROR)
78+
error_message = ErrorMessage::REFRESH_TOKEN_ERROR
79+
if debug
80+
error_message += "\nRefresh token used: #{format_token(refresh_token)}\n"
81+
error_message += error_details(error)
82+
else
83+
error_message += " #{debug_instructions}"
84+
end
85+
UI.user_error!(error_message)
7886
end
7987

8088
def service_account(google_service_path, debug)
@@ -86,14 +94,32 @@ def service_account(google_service_path, debug)
8694
rescue Errno::ENOENT
8795
UI.user_error!("#{ErrorMessage::SERVICE_CREDENTIALS_NOT_FOUND}: #{google_service_path}")
8896
rescue Signet::AuthorizationError => error
89-
log_authorization_error_details(error) if debug
90-
UI.user_error!("#{ErrorMessage::SERVICE_CREDENTIALS_ERROR}: #{google_service_path}")
97+
error_message = "#{ErrorMessage::SERVICE_CREDENTIALS_ERROR}: \"#{google_service_path}\""
98+
if debug
99+
error_message += "\n#{error_details(error)}"
100+
else
101+
error_message += ". #{debug_instructions}"
102+
end
103+
UI.user_error!(error_message)
104+
end
105+
106+
def error_details(error)
107+
"#{error.message}\nResponse status: #{error.response.status}"
108+
end
109+
110+
def debug_instructions
111+
"For more information, try again with firebase_app_distribution's \"debug\" parameter set to \"true\"."
91112
end
92113

93-
def log_authorization_error_details(error)
94-
UI.error("Error fetching access token:")
95-
UI.error(error.message)
96-
UI.error("Response status: #{error.response.status}")
114+
# Formats and redacts a token for printing out during debug logging. Examples:
115+
# 'abcd' -> '"abcd"''
116+
# 'abcdef1234' -> '"XXXXXf1234" (redacted)'
117+
def format_token(str)
118+
redaction_notice = str.length > REDACTION_EXPOSED_LENGTH ? " (redacted)" : ""
119+
exposed_start_char = [str.length - REDACTION_EXPOSED_LENGTH, 0].max
120+
exposed_characters = str[exposed_start_char, REDACTION_EXPOSED_LENGTH]
121+
redacted_characters = REDACTION_CHARACTER * [str.length - REDACTION_EXPOSED_LENGTH, 0].max
122+
"\"#{redacted_characters}#{exposed_characters}\"#{redaction_notice}"
97123
end
98124
end
99125
end

lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ module ErrorMessage
1313
INVALID_PATH = "Could not read content from"
1414
INVALID_TESTERS = "Could not enable access for testers. Check that the groups exist and the tester emails are formatted correctly"
1515
INVALID_RELEASE_NOTES = "Failed to add release notes"
16-
SERVICE_CREDENTIALS_ERROR = "App Distribution could not generate credentials from the service credentials file specified. Service Account Path"
16+
SERVICE_CREDENTIALS_ERROR = "App Distribution could not generate credentials from the service credentials file specified"
1717
PLAY_ACCOUNT_NOT_LINKED = "This project is not linked to a Google Play account."
1818
APP_NOT_PUBLISHED = "This app is not published in the Google Play console."
1919
NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT = "App with matching package name does not exist in Google Play."

spec/firebase_app_distribution_auth_client_spec.rb

+23-2
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,38 @@
9999
end
100100

101101
it 'crashes if the service credentials are invalid' do
102+
expect(fake_service_creds).to receive(:fetch_access_token!)
103+
.and_raise(Signet::AuthorizationError.new("error_message", { response: fake_error_response }))
104+
expect { auth_client.fetch_auth_token("invalid_service_path", empty_val, false) }
105+
.to raise_error("#{ErrorMessage::SERVICE_CREDENTIALS_ERROR}: \"invalid_service_path\". For more information, try again with firebase_app_distribution's \"debug\" parameter set to \"true\".")
106+
end
107+
108+
it 'crashes if the service credentials are invalid in debug mode' do
102109
expect(fake_service_creds).to receive(:fetch_access_token!)
103110
.and_raise(Signet::AuthorizationError.new("error_message", { response: fake_error_response }))
104111
expect { auth_client.fetch_auth_token("invalid_service_path", empty_val, true) }
105-
.to raise_error("#{ErrorMessage::SERVICE_CREDENTIALS_ERROR}: invalid_service_path")
112+
.to raise_error("#{ErrorMessage::SERVICE_CREDENTIALS_ERROR}: \"invalid_service_path\"\nerror_message\nResponse status: 400")
106113
end
107114

108115
it 'crashes if given an invalid firebase token' do
116+
expect(firebase_auth).to receive(:new)
117+
.and_raise(Signet::AuthorizationError.new("error_message", { response: fake_error_response }))
118+
expect { auth_client.fetch_auth_token(empty_val, "invalid_refresh_token", false) }
119+
.to raise_error("#{ErrorMessage::REFRESH_TOKEN_ERROR} For more information, try again with firebase_app_distribution's \"debug\" parameter set to \"true\".")
120+
end
121+
122+
it 'prints redacted token and error if given an invalid token in debug mode' do
109123
expect(firebase_auth).to receive(:new)
110124
.and_raise(Signet::AuthorizationError.new("error_message", { response: fake_error_response }))
111125
expect { auth_client.fetch_auth_token(empty_val, "invalid_refresh_token", true) }
112-
.to raise_error(ErrorMessage::REFRESH_TOKEN_ERROR)
126+
.to raise_error("#{ErrorMessage::REFRESH_TOKEN_ERROR}\nRefresh token used: \"XXXXXXXXXXXXXXXXtoken\" (redacted)\nerror_message\nResponse status: 400")
127+
end
128+
129+
it 'prints full token and error if given a short invalid token in debug mode' do
130+
expect(firebase_auth).to receive(:new)
131+
.and_raise(Signet::AuthorizationError.new("error_message", { response: fake_error_response }))
132+
expect { auth_client.fetch_auth_token(empty_val, "bad", true) }
133+
.to raise_error("#{ErrorMessage::REFRESH_TOKEN_ERROR}\nRefresh token used: \"bad\"\nerror_message\nResponse status: 400")
113134
end
114135

115136
it 'crashes if the firebase tools json has no tokens field' do

0 commit comments

Comments
 (0)