Repository: GoogleCloudPlatform/google-auth-library-python (python)

Issue:
Setting GOOGLE_APPLICATION_CREDENTIALS to empty string throws an error
I would expect an empty environment variable to act identically to an unset environment variable.

Reported by @parthea

If I set my GOOGLE_APPLICATION_CREDENTIALS environment variable to an empty string, I receive an exception instead of the local user web authentication flow. Is it expected?

```
tony@tonypc:~/pydata-pandas-gbq/pandas_gbq/tests$ export GOOGLE_APPLICATION_CREDENTIALS=''
tony@tonypc:~/pydata-pandas-gbq/pandas_gbq/tests$ pytest test_gbq.py::TestGBQConnectorIntegrationWithLocalUserAccountAuth -v -r s . --maxfail=1
============================= test session starts ==============================
platform linux2 -- Python 2.7.12, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- /home/tony/miniconda2/bin/python
cachedir: ../../.cache
rootdir: /home/tony/pydata-pandas-gbq, inifile: 
plugins: cov-2.4.0
collected 100 items 

test_gbq.py::TestGBQConnectorIntegrationWithLocalUserAccountAuth::test_should_be_able_to_make_a_connector ERROR

==================================== ERRORS ====================================
 ERROR at setup of TestGBQConnectorIntegrationWithLocalUserAccountAuth.test_should_be_able_to_make_a_connector 

self = <pandas_gbq.tests.test_gbq.TestGBQConnectorIntegrationWithLocalUserAccountAuth object at 0x7f2bbecf6490>
method = <bound method TestGBQConnectorIntegrationWithLocalUserAccountAuth.test_should_....TestGBQConnectorIntegrationWithLocalUserAccountAuth object at 0x7f2bbecf6490>>

    def setup_method(self, method):
        _setup_common()
        _skip_if_no_project_id()
        _skip_local_auth_if_in_travis_env()
    
>       self.sut = gbq.GbqConnector(_get_project_id(), auth_local_webserver=True)

test_gbq.py:268: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../gbq.py:174: in __init__
    auth_local_webserver=auth_local_webserver)
../gbq.py:182: in get_credentials
    credentials = self.get_application_default_credentials()
../gbq.py:212: in get_application_default_credentials
    credentials, _ = google.auth.default(scopes=[self.scope])
../../../miniconda2/lib/python2.7/site-packages/google/auth/_default.py:277: in default
    credentials, project_id = checker()
../../../miniconda2/lib/python2.7/site-packages/google/auth/_default.py:138: in _get_explicit_environ_credentials
    os.environ[environment_vars.CREDENTIALS])
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

filename = ''

    def _load_credentials_from_file(filename):
        """Loads credentials from a file.
    
        The credentials file must be a service account key or stored authorized
        user credentials.
    
        Args:
            filename (str): The full path to the credentials file.
    
        Returns:
            Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
                credentials and the project ID. Authorized user credentials do not
                have the project ID information.
    
        Raises:
            google.auth.exceptions.DefaultCredentialsError: if the file is in the
                wrong format.
        """
>       with io.open(filename, 'r') as file_obj:
E       IOError: [Errno 2] No such file or directory: ''

../../../miniconda2/lib/python2.7/site-packages/google/auth/_default.py:63: IOError
!!!!!!!!!!!!!!!!!!!! Interrupted: stopping after 1 failures !!!!!!!!!!!!!!!!!!!!
=========================== 1 error in 0.61 seconds ============================
```

Pull request:
Fix 161: Add error message to handle case of empty string for GOOGLE_APPLICATION_CREDENTIALS
Fixes #161 according to @jonparrott's recommendation

Patch:
diff --git a/google/auth/_default.py b/google/auth/_default.py
index 1e91368..7dac642 100644
--- a/google/auth/_default.py
+++ b/google/auth/_default.py
@@ -58,8 +58,12 @@ def _load_credentials_from_file(filename):
 
     Raises:
         google.auth.exceptions.DefaultCredentialsError: if the file is in the
-            wrong format.
+            wrong format or is missing.
     """
+    if not os.path.exists(filename):
+        raise exceptions.DefaultCredentialsError(
+            'File {} was not found.'.format(filename))
+
     with io.open(filename, 'r') as file_obj:
         try:
             info = json.load(file_obj)


Test patch:
diff --git a/tests/test__default.py b/tests/test__default.py
index 8054ac5..2df8a44 100644
--- a/tests/test__default.py
+++ b/tests/test__default.py
@@ -43,6 +43,13 @@ LOAD_FILE_PATCH = mock.patch(
         mock.sentinel.credentials, mock.sentinel.project_id), autospec=True)
 
 
+def test__load_credentials_from_missing_file():
+    with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
+        _default._load_credentials_from_file('')
+
+    assert excinfo.match(r'not found')
+
+
 def test__load_credentials_from_file_invalid_json(tmpdir):
     jsonfile = tmpdir.join('invalid.json')
     jsonfile.write('{')