diff --git a/googleauth.gemspec b/googleauth.gemspec index 1851851..63ff3d4 100755 --- a/googleauth.gemspec +++ b/googleauth.gemspec @@ -23,7 +23,8 @@ Gem::Specification.new do |gem| gem.required_ruby_version = ">= 2.7" gem.add_dependency "faraday", ">= 1.0", "< 3.a" - gem.add_dependency "google-cloud-env", "~> 2.1" + gem.add_dependency "google-cloud-env", "~> 2.2" + gem.add_dependency "google-logging-utils", "~> 0.1" gem.add_dependency "jwt", ">= 1.4", "< 3.0" gem.add_dependency "multi_json", "~> 1.11" gem.add_dependency "os", ">= 0.9", "< 2.0" diff --git a/lib/googleauth/base_client.rb b/lib/googleauth/base_client.rb index 52a1dba..3f1fdb9 100644 --- a/lib/googleauth/base_client.rb +++ b/lib/googleauth/base_client.rb @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +require "google/logging/message" + module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. @@ -29,7 +31,12 @@ def apply! a_hash, opts = {} # fetch the access token there is currently not one, or if the client # has expired fetch_access_token! opts if needs_access_token? - a_hash[AUTH_METADATA_KEY] = "Bearer #{send token_type}" + token = send token_type + a_hash[AUTH_METADATA_KEY] = "Bearer #{token}" + logger&.debug do + hash = Digest::SHA256.hexdigest token + Google::Logging::Message.from message: "Sending auth token. (sha256:#{hash})" + end end # Returns a clone of a_hash updated with the authentication token @@ -66,6 +73,9 @@ def expires_within? raise NoMethodError, "expires_within? not implemented" end + # The logger used to log operations on this client, such as token refresh. + attr_accessor :logger + private def token_type diff --git a/lib/googleauth/compute_engine.rb b/lib/googleauth/compute_engine.rb index 70025fa..a2464be 100644 --- a/lib/googleauth/compute_engine.rb +++ b/lib/googleauth/compute_engine.rb @@ -96,35 +96,71 @@ def initialize options = {} # Overrides the super class method to change how access tokens are # fetched. def fetch_access_token _options = {} - if token_type == :id_token - query = { "audience" => target_audience, "format" => "full" } - entry = "service-accounts/default/identity" - else - query = {} - entry = "service-accounts/default/token" - end + query, entry = + if token_type == :id_token + [{ "audience" => target_audience, "format" => "full" }, "service-accounts/default/identity"] + else + [{}, "service-accounts/default/token"] + end query[:scopes] = Array(scope).join "," if scope begin + log_fetch_query resp = Google::Cloud.env.lookup_metadata_response "instance", entry, query: query + log_fetch_resp resp case resp.status when 200 build_token_hash resp.body, resp.headers["content-type"], resp.retrieval_monotonic_time when 403, 500 - msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}" - raise Signet::UnexpectedStatusError, msg + raise Signet::UnexpectedStatusError, "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}" when 404 raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR else - msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}" - raise Signet::AuthorizationError, msg + raise Signet::AuthorizationError, "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}" end rescue Google::Cloud::Env::MetadataServerNotResponding => e + log_fetch_err e raise Signet::AuthorizationError, e.message end end private + def log_fetch_query + if token_type == :id_token + logger&.info do + Google::Logging::Message.from( + message: "Requesting id token from MDS with aud=#{target_audience}", + "credentialsId" => object_id + ) + end + else + logger&.info do + Google::Logging::Message.from( + message: "Requesting access token from MDS", + "credentialsId" => object_id + ) + end + end + end + + def log_fetch_resp resp + logger&.info do + Google::Logging::Message.from( + message: "Received #{resp.status} from MDS", + "credentialsId" => object_id + ) + end + end + + def log_fetch_err _err + logger&.info do + Google::Logging::Message.from( + message: "MDS did not respond to token request", + "credentialsId" => object_id + ) + end + end + def build_token_hash body, content_type, retrieval_time hash = if ["text/html", "application/text"].include? content_type diff --git a/lib/googleauth/credentials.rb b/lib/googleauth/credentials.rb index 733c89b..f4404d7 100644 --- a/lib/googleauth/credentials.rb +++ b/lib/googleauth/credentials.rb @@ -337,10 +337,13 @@ def disable_universe_domain_check # @!attribute [rw] universe_domain # @return [String] The universe domain issuing these credentials. # + # @!attribute [rw] logger + # @return [Logger] The logger used to log credential operations such as token refresh. + # def_delegators :@client, :token_credential_uri, :audience, :scope, :issuer, :signing_key, :updater_proc, :target_audience, - :universe_domain, :universe_domain= + :universe_domain, :universe_domain=, :logger, :logger= ## # Creates a new Credentials instance with the provided auth credentials, and with the default @@ -349,16 +352,17 @@ def disable_universe_domain_check # @param [String, Hash, Signet::OAuth2::Client] keyfile # The keyfile can be provided as one of the following: # - # * The path to a JSON keyfile (as a +String+) - # * The contents of a JSON keyfile (as a +Hash+) - # * A +Signet::OAuth2::Client+ object + # * The path to a JSON keyfile (as a `String`) + # * The contents of a JSON keyfile (as a `Hash`) + # * A `Signet::OAuth2::Client` object # @param [Hash] options # The options for configuring the credentials instance. The following is supported: # - # * +:scope+ - the scope for the client - # * +"project_id"+ (and optionally +"project"+) - the project identifier for the client - # * +:connection_builder+ - the connection builder to use for the client - # * +:default_connection+ - the default connection to use for the client + # * `:scope` - the scope for the client + # * `project_id` (and optionally `project`) - the project identifier for the client + # * `:connection_builder` - the connection builder to use for the client + # * `:default_connection` - the default connection to use for the client + # * `:logger` - the logger used to log credential operations such as token refresh. # def initialize keyfile, options = {} verify_keyfile_provided! keyfile @@ -373,8 +377,8 @@ def initialize keyfile, options = {} else update_from_filepath keyfile, options end + setup_logging logger: options.fetch(:logger, :default) @project_id ||= CredentialsLoader.load_gcloud_project_id - @client.fetch_access_token! if @client.needs_access_token? @env_vars = nil @paths = nil @scope = nil @@ -468,7 +472,8 @@ def self.from_io io, options audience: options[:audience] || audience } client = Google::Auth::DefaultCredentials.make_creds creds_input - new client + options = options.select { |k, _v| k == :logger } + new client, options end private_class_method :from_env_vars, @@ -549,6 +554,23 @@ def update_from_filepath path, options @quota_project_id ||= json["quota_project_id"] @client = init_client json, options end + + def setup_logging logger: :default + return unless @client.respond_to? :logger= + logging_env = ENV["GOOGLE_SDK_RUBY_LOGGING_GEMS"].to_s.downcase + if ["false", "none"].include? logging_env + logger = nil + elsif @client.logger + logger = @client.logger + elsif logger == :default + logger = nil + if ["true", "all"].include?(logging_env) || logging_env.split(",").include?("googleauth") + formatter = Google::Logging::StructuredFormatter.new if Google::Cloud::Env.get.logging_agent_expected? + logger = Logger.new $stderr, progname: "googleauth", formatter: formatter + end + end + @client.logger = logger + end end end end diff --git a/lib/googleauth/external_account/base_credentials.rb b/lib/googleauth/external_account/base_credentials.rb index 80663d7..3169124 100644 --- a/lib/googleauth/external_account/base_credentials.rb +++ b/lib/googleauth/external_account/base_credentials.rb @@ -129,7 +129,7 @@ def exchange_token if @client_id.nil? && @workforce_pool_user_project additional_options = { userProject: @workforce_pool_user_project } end - @sts_client.exchange_token( + token_request = { audience: @audience, grant_type: STS_GRANT_TYPE, subject_token: retrieve_subject_token!, @@ -137,10 +137,31 @@ def exchange_token scopes: @service_account_impersonation_url ? IAM_SCOPE : @scope, requested_token_type: STS_REQUESTED_TOKEN_TYPE, additional_options: additional_options - ) + } + log_token_request token_request + @sts_client.exchange_token token_request + end + + def log_token_request token_request + logger&.info do + Google::Logging::Message.from( + message: "Requesting access token from #{STS_GRANT_TYPE}", + "credentialsId" => object_id + ) + end + logger&.debug do + digest = Digest::SHA256.hexdigest token_request[:subject_token].to_s + loggable_request = token_request.merge subject_token: "(sha256:#{digest})" + Google::Logging::Message.from( + message: "Request data", + "request" => loggable_request, + "credentialsId" => object_id + ) + end end def get_impersonated_access_token token, _options = {} + log_impersonated_token_request token response = connection.post @service_account_impersonation_url do |req| req.headers["Authorization"] = "Bearer #{token}" req.headers["Content-Type"] = "application/json" @@ -153,6 +174,16 @@ def get_impersonated_access_token token, _options = {} MultiJson.load response.body end + + def log_impersonated_token_request original_token + logger&.info do + digest = Digest::SHA256.hexdigest original_token + Google::Logging::Message.from( + message: "Requesting impersonated access token with original token (sha256:#{digest})", + "credentialsId" => object_id + ) + end + end end end end diff --git a/lib/googleauth/service_account.rb b/lib/googleauth/service_account.rb index cbfca82..60f0db4 100644 --- a/lib/googleauth/service_account.rb +++ b/lib/googleauth/service_account.rb @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +require "google/logging/message" require "googleauth/signet" require "googleauth/credentials_loader" require "googleauth/json_key_reader" @@ -123,6 +124,7 @@ def apply_self_signed_jwt! a_hash } key_io = StringIO.new MultiJson.dump(cred_json) alt = ServiceAccountJwtHeaderCredentials.make_creds json_key_io: key_io, scope: scope + alt.logger = logger alt.apply! a_hash end end @@ -147,6 +149,7 @@ class ServiceAccountJwtHeaderCredentials attr_reader :project_id attr_reader :quota_project_id attr_accessor :universe_domain + attr_accessor :logger # Create a ServiceAccountJwtHeaderCredentials. # @@ -187,10 +190,14 @@ def apply! a_hash, opts = {} return a_hash if jwt_aud_uri.nil? && @scope.nil? jwt_token = new_jwt_token jwt_aud_uri, opts a_hash[AUTH_METADATA_KEY] = "Bearer #{jwt_token}" + logger&.debug do + hash = Digest::SHA256.hexdigest jwt_token + Google::Logging::Message.from message: "Sending JWT auth token. (sha256:#{hash})" + end a_hash end - # Returns a clone of a_hash updated with the authoriation header + # Returns a clone of a_hash updated with the authorization header def apply a_hash, opts = {} a_copy = a_hash.clone apply! a_copy, opts @@ -219,6 +226,10 @@ def new_jwt_token jwt_aud_uri = nil, options = {} assertion["scope"] = Array(@scope).join " " if @scope assertion["aud"] = jwt_aud_uri if jwt_aud_uri + logger&.debug do + Google::Logging::Message.from message: "JWT assertion: #{assertion}" + end + JWT.encode assertion, @signing_key, SIGNING_ALGORITHM end diff --git a/lib/googleauth/signet.rb b/lib/googleauth/signet.rb index 1c3016d..316a49c 100644 --- a/lib/googleauth/signet.rb +++ b/lib/googleauth/signet.rb @@ -65,6 +65,24 @@ def fetch_access_token! options = {} info end + alias googleauth_orig_generate_access_token_request generate_access_token_request + def generate_access_token_request options = {} + parameters = googleauth_orig_generate_access_token_request options + logger&.info do + Google::Logging::Message.from( + message: "Requesting access token from #{parameters['grant_type']}", + "credentialsId" => object_id + ) + end + logger&.debug do + Google::Logging::Message.from( + message: "Token fetch params: #{parameters}", + "credentialsId" => object_id + ) + end + parameters + end + def build_default_connection if !defined?(@connection_info) nil @@ -79,15 +97,20 @@ def retry_with_error max_retry_count = 5 retry_count = 0 begin - yield + yield.tap { |resp| log_response resp } rescue StandardError => e - raise e if e.is_a?(Signet::AuthorizationError) || e.is_a?(Signet::ParseError) + if e.is_a?(Signet::AuthorizationError) || e.is_a?(Signet::ParseError) + log_auth_error e + raise e + end if retry_count < max_retry_count + log_transient_error e retry_count += 1 sleep retry_count * 0.3 retry else + log_retries_exhausted e msg = "Unexpected error: #{e.inspect}" raise Signet::AuthorizationError, msg end @@ -106,6 +129,49 @@ def expires_at_from_id_token id_token # Shouldn't happen unless we get a garbled ID token nil end + + def log_response token_response + response_hash = JSON.parse token_response rescue {} + if response_hash["access_token"] + digest = Digest::SHA256.hexdigest response_hash["access_token"] + response_hash["access_token"] = "(sha256:#{digest})" + end + if response_hash["id_token"] + digest = Digest::SHA256.hexdigest response_hash["id_token"] + response_hash["id_token"] = "(sha256:#{digest})" + end + Google::Logging::Message.from( + message: "Received auth token response: #{response_hash}", + "credentialsId" => object_id + ) + end + + def log_auth_error err + logger&.info do + Google::Logging::Message.from( + message: "Auth error when fetching auth token: #{err}", + "credentialsId" => object_id + ) + end + end + + def log_transient_error err + logger&.info do + Google::Logging::Message.from( + message: "Transient error when fetching auth token: #{err}", + "credentialsId" => object_id + ) + end + end + + def log_retries_exhausted err + logger&.info do + Google::Logging::Message.from( + message: "Exhausted retries when fetching auth token: #{err}", + "credentialsId" => object_id + ) + end + end end end end diff --git a/spec/googleauth/apply_auth_examples.rb b/spec/googleauth/apply_auth_examples.rb index 88f7cdc..3027a86 100644 --- a/spec/googleauth/apply_auth_examples.rb +++ b/spec/googleauth/apply_auth_examples.rb @@ -62,6 +62,26 @@ )) expect(access_stub).to have_been_requested end + + it "should log when a logger is set" do + access_stub + io = StringIO.new + @client.logger = Logger.new io + @client.fetch_access_token! + expect(io.string).to include "INFO -- : Requesting access token from" + end + + it "should not log to stdout when a logger is not set" do + access_stub + @client.logger = nil + expect { @client.fetch_access_token! }.to_not output.to_stdout + end + + it "should not log to stderr when a logger is not set" do + access_stub + @client.logger = nil + expect { @client.fetch_access_token! }.to_not output.to_stderr + end end describe "#apply!" do diff --git a/spec/googleauth/credentials_spec.rb b/spec/googleauth/credentials_spec.rb index 3eae46c..0cadd78 100644 --- a/spec/googleauth/credentials_spec.rb +++ b/spec/googleauth/credentials_spec.rb @@ -33,6 +33,9 @@ } end let(:default_keyfile_content) { JSON.generate default_keyfile_hash } + let(:fake_path_1) { "/fake/path/to/file.txt".freeze } + let(:fake_path_2) { "/unknown/path/to/file.txt".freeze } + FAKE_DEFAULT_PATH = "/default/path/to/file.txt".freeze def stub_token_request access_token: nil, id_token: nil, uri: nil body_fields = { "token_type" => "Bearer", "expires_in" => 3600 } @@ -73,14 +76,82 @@ def stub_metadata_request expect(client.signing_key).to be_a_kind_of(OpenSSL::PKey::RSA) end + describe "logger" do + after :example do + ENV["TEST_JSON_VARS"] = nil + ENV["GOOGLE_APPLICATION_CREDENTIALS"] = nil + end + + it "defaults to nil" do + creds = Google::Auth::Credentials.new default_keyfile_hash + expect(creds.logger).to be_nil + end + + it "takes a logger on the constructor" do + my_logger = Logger.new $stderr + creds = Google::Auth::Credentials.new default_keyfile_hash, logger: my_logger + expect(creds.logger).to equal(my_logger) + end + + it "uses the logger in a provided signet client rather than a passed in logger" do + my_logger = Logger.new $stderr + wrong_logger = Logger.new $stderr + signet = Signet::OAuth2::Client.new access_token: token + signet.logger = my_logger + creds = Google::Auth::Credentials.new signet, logger: wrong_logger + expect(creds.logger).to equal(my_logger) + end + + it "allows logger to be set explicitly" do + my_logger = Logger.new $stderr + creds = Google::Auth::Credentials.new default_keyfile_hash + creds.logger = my_logger + expect(creds.logger).to equal(my_logger) + end + + class TestCredentialsForLogging < Google::Auth::Credentials + TOKEN_CREDENTIAL_URI = "https://example.com/token".freeze + AUDIENCE = "https://example.com/audience".freeze + SCOPE = "http://example.com/scope".freeze + JSON_ENV_VARS = ["TEST_JSON_VARS"].freeze + end + + it "allows logger to be set when getting an adc-based default credential" do + my_logger = Logger.new $stderr + ENV["GOOGLE_APPLICATION_CREDENTIALS"] = fake_path_1 + allow(::File).to receive(:exist?).with(fake_path_1) { true } + allow(::File).to receive(:open).with(fake_path_1).and_yield(StringIO.new default_keyfile_content) + creds = TestCredentialsForLogging.default logger: my_logger + expect(creds.logger).to equal(my_logger) + end + + it "allows logger to be set when getting an io-based default credential" do + test_json_env_val = JSON.generate default_keyfile_hash + ENV["TEST_JSON_VARS"] = test_json_env_val + allow(::File).to receive(:file?).with(test_json_env_val) { false } + my_logger = Logger.new $stderr + creds = TestCredentialsForLogging.default logger: my_logger + expect(creds.logger).to equal(my_logger) + end + end + it "uses empty paths and env_vars by default" do expect(Google::Auth::Credentials.paths).to eq([]) expect(Google::Auth::Credentials.env_vars).to eq([]) end describe "subclasses using CONSTANTS" do + after :example do + ENV["TEST_PATH"] = nil + ENV["TEST_JSON_VARS"] = nil + ENV["PATH_ENV_DUMMY"] = nil + ENV["PATH_ENV_TEST"] = nil + ENV["JSON_ENV_DUMMY"] = nil + ENV["JSON_ENV_TEST"] = nil + end + it "passes in other env paths" do - test_path_env_val = "/unknown/path/to/file.txt".freeze + test_path_env_val = fake_path_1 test_json_env_val = JSON.generate default_keyfile_hash ENV["TEST_PATH"] = test_path_env_val @@ -117,16 +188,14 @@ class TestCredentials2 < Google::Auth::Credentials SCOPE = "http://example.com/scope".freeze PATH_ENV_VARS = %w[PATH_ENV_DUMMY PATH_ENV_TEST].freeze JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze - DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze + DEFAULT_PATHS = [FAKE_DEFAULT_PATH].freeze end - allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" } - allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false } - allow(::ENV).to receive(:[]).with("PATH_ENV_TEST") { "/unknown/path/to/file.txt" } - allow(::ENV).to receive(:[]).with("https_proxy") { nil } - allow(::ENV).to receive(:[]).with("HTTPS_PROXY") { nil } - allow(::File).to receive(:file?).with("/unknown/path/to/file.txt") { true } - allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { default_keyfile_content } + ENV["PATH_ENV_DUMMY"] = fake_path_1 + ENV["PATH_ENV_TEST"] = fake_path_2 + allow(::File).to receive(:file?).with(fake_path_1) { false } + allow(::File).to receive(:file?).with(fake_path_2) { true } + allow(::File).to receive(:read).with(fake_path_2) { default_keyfile_content } stub_token_request @@ -152,16 +221,13 @@ class TestCredentials3 < Google::Auth::Credentials SCOPE = "http://example.com/scope".freeze PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze JSON_ENV_VARS = %w[JSON_ENV_DUMMY JSON_ENV_TEST].freeze - DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze + DEFAULT_PATHS = [FAKE_DEFAULT_PATH].freeze end - allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" } - allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false } + ENV["PATH_ENV_DUMMY"] = fake_path_1 + ENV["JSON_ENV_TEST"] = test_json_env_val + allow(::File).to receive(:file?).with(fake_path_1) { false } allow(::File).to receive(:file?).with(test_json_env_val) { false } - allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil } - allow(::ENV).to receive(:[]).with("JSON_ENV_TEST") { test_json_env_val } - allow(::ENV).to receive(:[]).with("https_proxy") { nil } - allow(::ENV).to receive(:[]).with("HTTPS_PROXY") { nil } stub_token_request @@ -184,16 +250,13 @@ class TestCredentials4 < Google::Auth::Credentials SCOPE = "http://example.com/scope".freeze PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze - DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze + DEFAULT_PATHS = [FAKE_DEFAULT_PATH].freeze end - allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" } - allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false } - allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil } - allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { true } - allow(::File).to receive(:read).with("~/default/path/to/file.txt") { default_keyfile_content } - allow(::ENV).to receive(:[]).with("https_proxy") { nil } - allow(::ENV).to receive(:[]).with("HTTPS_PROXY") { nil } + ENV["PATH_ENV_DUMMY"] = fake_path_1 + allow(::File).to receive(:file?).with(fake_path_1) { false } + allow(::File).to receive(:file?).with(FAKE_DEFAULT_PATH) { true } + allow(::File).to receive(:read).with(FAKE_DEFAULT_PATH) { default_keyfile_content } stub_token_request @@ -217,17 +280,18 @@ class TestCredentials5 < Google::Auth::Credentials SCOPE = "http://example.com/scope".freeze PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze - DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze + DEFAULT_PATHS = [FAKE_DEFAULT_PATH].freeze end - allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" } - allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false } + allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { fake_path_1 } + allow(::File).to receive(:file?).with(fake_path_1) { false } allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil } allow(::ENV).to receive(:[]).with("OS") { nil } allow(::ENV).to receive(:[]).with("HOME") { nil } allow(::ENV).to receive(:[]).with("APPDATA") { nil } allow(::ENV).to receive(:[]).with("ProgramData") { nil } - allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false } + allow(::ENV).to receive(:[]).with("GOOGLE_SDK_RUBY_LOGGING_GEMS") { nil } + allow(::File).to receive(:file?).with(FAKE_DEFAULT_PATH) { false } # stub_token_request @@ -253,7 +317,7 @@ class TestCredentials6 < Google::Auth::Credentials SCOPE = "http://example.com/scope".freeze PATH_ENV_VARS = ["TEST_PATH"].freeze JSON_ENV_VARS = ["TEST_JSON_VARS"].freeze - DEFAULT_PATHS = ["~/default/path/to/file.txt"] + DEFAULT_PATHS = [FAKE_DEFAULT_PATH] end class TestCredentials7 < TestCredentials6 @@ -263,7 +327,7 @@ class TestCredentials7 < TestCredentials6 expect(TestCredentials7.audience).to eq("https://example.com/audience") expect(TestCredentials7.scope).to eq(["http://example.com/scope"]) expect(TestCredentials7.env_vars).to eq(["TEST_PATH", "TEST_JSON_VARS"]) - expect(TestCredentials7.paths).to eq(["~/default/path/to/file.txt"]) + expect(TestCredentials7.paths).to eq([FAKE_DEFAULT_PATH]) TestCredentials7::TOKEN_CREDENTIAL_URI = "https://example.com/token2" expect(TestCredentials7.token_credential_uri).to eq("https://example.com/token2") @@ -273,13 +337,17 @@ class TestCredentials7 < TestCredentials6 end describe "subclasses using class methods" do - it "passes in other env paths" do - test_path_env_val = "/unknown/path/to/file.txt".freeze - test_json_env_val = JSON.generate default_keyfile_hash - - ENV["TEST_PATH"] = test_path_env_val - ENV["TEST_JSON_VARS"] = test_json_env_val + after :example do + ENV["TEST_PATH"] = nil + ENV["TEST_JSON_VARS"] = nil + ENV["PATH_ENV_DUMMY"] = nil + ENV["PATH_ENV_TEST"] = nil + ENV["JSON_ENV_DUMMY"] = nil + ENV["JSON_ENV_TEST"] = nil + ENV["GOOGLE_APPLICATION_CREDENTIALS"] = nil + end + it "passes in other env paths" do class TestCredentials11 < Google::Auth::Credentials self.token_credential_uri = "https://example.com/token" self.audience = "https://example.com/audience" @@ -287,7 +355,10 @@ class TestCredentials11 < Google::Auth::Credentials self.env_vars = ["TEST_PATH", "TEST_JSON_VARS"] end - allow(::File).to receive(:file?).with(test_path_env_val) { false } + test_json_env_val = JSON.generate default_keyfile_hash + ENV["TEST_PATH"] = fake_path_2 + ENV["TEST_JSON_VARS"] = test_json_env_val + allow(::File).to receive(:file?).with(fake_path_2) { false } allow(::File).to receive(:file?).with(test_json_env_val) { false } stub_token_request uri: "https://example.com/token" @@ -311,16 +382,14 @@ class TestCredentials11 < Google::Auth::Credentials class TestCredentials12 < Google::Auth::Credentials self.scope = "http://example.com/scope" self.env_vars = %w[PATH_ENV_DUMMY PATH_ENV_TEST JSON_ENV_DUMMY] - self.paths = ["~/default/path/to/file.txt"] + self.paths = [FAKE_DEFAULT_PATH] end - allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" } - allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false } - allow(::ENV).to receive(:[]).with("PATH_ENV_TEST") { "/unknown/path/to/file.txt" } - allow(::File).to receive(:file?).with("/unknown/path/to/file.txt") { true } - allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { default_keyfile_content } - allow(::ENV).to receive(:[]).with("https_proxy") { nil } - allow(::ENV).to receive(:[]).with("HTTPS_PROXY") { nil } + ENV["PATH_ENV_DUMMY"] = fake_path_1 + ENV["PATH_ENV_TEST"] = fake_path_2 + allow(::File).to receive(:file?).with(fake_path_1) { false } + allow(::File).to receive(:file?).with(fake_path_2) { true } + allow(::File).to receive(:read).with(fake_path_2) { default_keyfile_content } stub_token_request @@ -343,16 +412,13 @@ class TestCredentials12 < Google::Auth::Credentials class TestCredentials13 < Google::Auth::Credentials self.scope = "http://example.com/scope" self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY JSON_ENV_TEST] - self.paths = ["~/default/path/to/file.txt"] + self.paths = [FAKE_DEFAULT_PATH] end - allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" } - allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false } + ENV["PATH_ENV_DUMMY"] = fake_path_1 + ENV["JSON_ENV_TEST"] = default_keyfile_content + allow(::File).to receive(:file?).with(fake_path_1) { false } allow(::File).to receive(:file?).with(default_keyfile_content) { false } - allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil } - allow(::ENV).to receive(:[]).with("JSON_ENV_TEST") { default_keyfile_content } - allow(::ENV).to receive(:[]).with("https_proxy") { nil } - allow(::ENV).to receive(:[]).with("HTTPS_PROXY") { nil } stub_token_request @@ -375,16 +441,13 @@ class TestCredentials13 < Google::Auth::Credentials class TestCredentials14 < Google::Auth::Credentials self.scope = "http://example.com/scope" self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY] - self.paths = ["~/default/path/to/file.txt"] + self.paths = [FAKE_DEFAULT_PATH] end - allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" } - allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false } - allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil } - allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { true } - allow(::File).to receive(:read).with("~/default/path/to/file.txt") { default_keyfile_content } - allow(::ENV).to receive(:[]).with("https_proxy") { nil } - allow(::ENV).to receive(:[]).with("HTTPS_PROXY") { nil } + ENV["PATH_ENV_DUMMY"] = fake_path_1 + allow(::File).to receive(:file?).with(fake_path_1) { false } + allow(::File).to receive(:file?).with(FAKE_DEFAULT_PATH) { true } + allow(::File).to receive(:read).with(FAKE_DEFAULT_PATH) { default_keyfile_content } stub_token_request @@ -407,17 +470,15 @@ class TestCredentials14 < Google::Auth::Credentials class TestCredentials15 < Google::Auth::Credentials self.scope = "http://example.com/scope" self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY] - self.paths = ["~/default/path/to/file.txt"] + self.paths = [FAKE_DEFAULT_PATH] end - allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" } - allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false } - allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil } - allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false } - allow(::ENV).to receive(:key?).with("GOOGLE_APPLICATION_CREDENTIALS") { true } - allow(::ENV).to receive(:[]).with("GOOGLE_APPLICATION_CREDENTIALS") { "/adc/path/to/file.txt" } - allow(::File).to receive(:exist?).with("/adc/path/to/file.txt") { true } - allow(::File).to receive(:open).with("/adc/path/to/file.txt").and_yield(StringIO.new default_keyfile_content) + ENV["PATH_ENV_DUMMY"] = fake_path_1 + ENV["GOOGLE_APPLICATION_CREDENTIALS"] = fake_path_2 + allow(::File).to receive(:file?).with(fake_path_1) { false } + allow(::File).to receive(:file?).with(FAKE_DEFAULT_PATH) { false } + allow(::File).to receive(:exist?).with(fake_path_2) { true } + allow(::File).to receive(:open).with(fake_path_2).and_yield(StringIO.new default_keyfile_content) creds = TestCredentials15.default enable_self_signed_jwt: true expect(creds).to be_a_kind_of(TestCredentials15) @@ -438,19 +499,15 @@ class TestCredentials15 < Google::Auth::Credentials class TestCredentials16 < Google::Auth::Credentials self.scope = "http://example.com/scope" self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY] - self.paths = ["~/default/path/to/file.txt"] + self.paths = [FAKE_DEFAULT_PATH] end - allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" } - allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false } - allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil } - allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false } - allow(::ENV).to receive(:key?).with("GOOGLE_APPLICATION_CREDENTIALS") { true } - allow(::ENV).to receive(:[]).with("GOOGLE_APPLICATION_CREDENTIALS") { "/adc/path/to/file.txt" } - allow(::File).to receive(:exist?).with("/adc/path/to/file.txt") { true } - allow(::File).to receive(:open).with("/adc/path/to/file.txt").and_yield(StringIO.new default_keyfile_content) - allow(::ENV).to receive(:[]).with("https_proxy") { nil } - allow(::ENV).to receive(:[]).with("HTTPS_PROXY") { nil } + ENV["PATH_ENV_DUMMY"] = fake_path_1 + ENV["GOOGLE_APPLICATION_CREDENTIALS"] = fake_path_2 + allow(::File).to receive(:file?).with(fake_path_1) { false } + allow(::File).to receive(:file?).with(FAKE_DEFAULT_PATH) { false } + allow(::File).to receive(:exist?).with(fake_path_2) { true } + allow(::File).to receive(:open).with(fake_path_2).and_yield(StringIO.new default_keyfile_content) stub_token_request @@ -475,21 +532,17 @@ class TestCredentials16 < Google::Auth::Credentials class TestCredentials17 < Google::Auth::Credentials self.scope = "http://example.com/scope" self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY] - self.paths = ["~/default/path/to/file.txt"] + self.paths = [FAKE_DEFAULT_PATH] self.token_credential_uri = "https://example.com/token2" self.audience = "https://example.com/token3" end - allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" } - allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false } - allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil } - allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false } - allow(::ENV).to receive(:key?).with("GOOGLE_APPLICATION_CREDENTIALS") { true } - allow(::ENV).to receive(:[]).with("GOOGLE_APPLICATION_CREDENTIALS") { "/adc/path/to/file.txt" } - allow(::File).to receive(:exist?).with("/adc/path/to/file.txt") { true } - allow(::File).to receive(:open).with("/adc/path/to/file.txt").and_yield(StringIO.new default_keyfile_content) - allow(::ENV).to receive(:[]).with("https_proxy") { nil } - allow(::ENV).to receive(:[]).with("HTTPS_PROXY") { nil } + ENV["PATH_ENV_DUMMY"] = fake_path_1 + ENV["GOOGLE_APPLICATION_CREDENTIALS"] = fake_path_2 + allow(::File).to receive(:file?).with(fake_path_1) { false } + allow(::File).to receive(:file?).with(FAKE_DEFAULT_PATH) { false } + allow(::File).to receive(:exist?).with(fake_path_2) { true } + allow(::File).to receive(:open).with(fake_path_2).and_yield(StringIO.new default_keyfile_content) stub_token_request uri: "https://example.com/token2" @@ -513,7 +566,7 @@ class TestCredentials18 < Google::Auth::Credentials self.scope = "http://example.com/scope" self.target_audience = "https://example.com/target_audience" self.env_vars = ["TEST_PATH", "TEST_JSON_VARS"] - self.paths = ["~/default/path/to/file.txt"] + self.paths = [FAKE_DEFAULT_PATH] end class TestCredentials19 < TestCredentials18 @@ -522,7 +575,7 @@ class TestCredentials19 < TestCredentials18 expect(TestCredentials19.scope).to eq(["http://example.com/scope"]) expect(TestCredentials19.target_audience).to eq("https://example.com/target_audience") expect(TestCredentials19.env_vars).to eq(["TEST_PATH", "TEST_JSON_VARS"]) - expect(TestCredentials19.paths).to eq(["~/default/path/to/file.txt"]) + expect(TestCredentials19.paths).to eq([FAKE_DEFAULT_PATH]) TestCredentials19.token_credential_uri = "https://example.com/token2" expect(TestCredentials19.token_credential_uri).to eq("https://example.com/token2") @@ -535,7 +588,7 @@ class TestCredentials19 < TestCredentials18 class TestCredentials20 < Google::Auth::Credentials self.scope = "http://example.com/scope" self.env_vars = ["TEST_PATH", "TEST_JSON_VARS"] - self.paths = ["~/default/path/to/file.txt"] + self.paths = [FAKE_DEFAULT_PATH] end Dir.mktmpdir do |dir|