Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make load_credentials_from_file a public method #530

Merged
merged 10 commits into from
Jun 18, 2020
4 changes: 2 additions & 2 deletions google/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@

import logging

from google.auth._default import default
from google.auth._default import default, load_credentials_from_file


__all__ = ["default"]
__all__ = ["default", "load_credentials_from_file"]


# Set default logging handler to avoid "No handler found" warnings.
Expand Down
19 changes: 13 additions & 6 deletions google/auth/_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,17 @@ def _warn_about_problematic_credentials(credentials):
warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING)


def _load_credentials_from_file(filename):
"""Loads credentials from a file.
def load_credentials_from_file(filename, scopes=None):
"""Loads Google 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.
scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
specified, the credentials will automatically be scoped if
necessary.

Returns:
Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
Expand Down Expand Up @@ -109,7 +112,9 @@ def _load_credentials_from_file(filename):
from google.oauth2 import credentials

try:
credentials = credentials.Credentials.from_authorized_user_info(info)
credentials = credentials.Credentials.from_authorized_user_info(
info, scopes=scopes
)
except ValueError as caught_exc:
msg = "Failed to load authorized user credentials from {}".format(filename)
new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
Expand All @@ -122,7 +127,9 @@ def _load_credentials_from_file(filename):
from google.oauth2 import service_account

try:
credentials = service_account.Credentials.from_service_account_info(info)
credentials = service_account.Credentials.from_service_account_info(
info, scopes=scopes
)
except ValueError as caught_exc:
msg = "Failed to load service account credentials from {}".format(filename)
new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
Expand All @@ -148,7 +155,7 @@ def _get_gcloud_sdk_credentials():
if not os.path.isfile(credentials_filename):
return None, None

credentials, project_id = _load_credentials_from_file(credentials_filename)
credentials, project_id = load_credentials_from_file(credentials_filename)

if not project_id:
project_id = _cloud_sdk.get_project_id()
Expand All @@ -162,7 +169,7 @@ def _get_explicit_environ_credentials():
explicit_file = os.environ.get(environment_vars.CREDENTIALS)

if explicit_file is not None:
credentials, project_id = _load_credentials_from_file(
credentials, project_id = load_credentials_from_file(
os.environ[environment_vars.CREDENTIALS]
)

Expand Down
68 changes: 50 additions & 18 deletions tests/test__default.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,88 +43,120 @@

SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json")

CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, "client_secrets.json")

with open(SERVICE_ACCOUNT_FILE) as fh:
SERVICE_ACCOUNT_FILE_DATA = json.load(fh)

LOAD_FILE_PATCH = mock.patch(
"google.auth._default._load_credentials_from_file",
"google.auth._default.load_credentials_from_file",
return_value=(mock.sentinel.credentials, mock.sentinel.project_id),
autospec=True,
)


def test__load_credentials_from_missing_file():
def test_load_credentials_from_missing_file():
with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
_default._load_credentials_from_file("")
_default.load_credentials_from_file("")

assert excinfo.match(r"not found")


def test__load_credentials_from_file_invalid_json(tmpdir):
def test_load_credentials_from_file_invalid_json(tmpdir):
jsonfile = tmpdir.join("invalid.json")
jsonfile.write("{")

with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
_default._load_credentials_from_file(str(jsonfile))
_default.load_credentials_from_file(str(jsonfile))

assert excinfo.match(r"not a valid json file")


def test__load_credentials_from_file_invalid_type(tmpdir):
def test_load_credentials_from_file_invalid_type(tmpdir):
jsonfile = tmpdir.join("invalid.json")
jsonfile.write(json.dumps({"type": "not-a-real-type"}))

with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
_default._load_credentials_from_file(str(jsonfile))
_default.load_credentials_from_file(str(jsonfile))

assert excinfo.match(r"does not have a valid type")


def test__load_credentials_from_file_authorized_user():
credentials, project_id = _default._load_credentials_from_file(AUTHORIZED_USER_FILE)
def test_load_credentials_from_file_authorized_user():
credentials, project_id = _default.load_credentials_from_file(AUTHORIZED_USER_FILE)
assert isinstance(credentials, google.oauth2.credentials.Credentials)
assert project_id is None


def test__load_credentials_from_file_authorized_user_bad_format(tmpdir):
def test_load_credentials_from_file_no_type(tmpdir):
# use the client_secrets.json, which is valid json but not a
# loadable credentials type
with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
_default.load_credentials_from_file(CLIENT_SECRETS_FILE)

assert excinfo.match(r"does not have a valid type")
assert excinfo.match(r"Type is None")


def test_load_credentials_from_file_authorized_user_bad_format(tmpdir):
filename = tmpdir.join("authorized_user_bad.json")
filename.write(json.dumps({"type": "authorized_user"}))

with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
_default._load_credentials_from_file(str(filename))
_default.load_credentials_from_file(str(filename))

assert excinfo.match(r"Failed to load authorized user")
assert excinfo.match(r"missing fields")


def test__load_credentials_from_file_authorized_user_cloud_sdk():
def test_load_credentials_from_file_authorized_user_cloud_sdk():
with pytest.warns(UserWarning, match="Cloud SDK"):
credentials, project_id = _default._load_credentials_from_file(
credentials, project_id = _default.load_credentials_from_file(
AUTHORIZED_USER_CLOUD_SDK_FILE
)
assert isinstance(credentials, google.oauth2.credentials.Credentials)
assert project_id is None

# No warning if the json file has quota project id.
credentials, project_id = _default._load_credentials_from_file(
credentials, project_id = _default.load_credentials_from_file(
AUTHORIZED_USER_CLOUD_SDK_WITH_QUOTA_PROJECT_ID_FILE
)
assert isinstance(credentials, google.oauth2.credentials.Credentials)
assert project_id is None


def test__load_credentials_from_file_service_account():
credentials, project_id = _default._load_credentials_from_file(SERVICE_ACCOUNT_FILE)
def test_load_credentials_from_file_authorized_user_cloud_sdk_with_scopes():
with pytest.warns(UserWarning, match="Cloud SDK"):
credentials, project_id = _default.load_credentials_from_file(
AUTHORIZED_USER_CLOUD_SDK_FILE,
scopes=["https://www.google.com/calendar/feeds"],
)
assert isinstance(credentials, google.oauth2.credentials.Credentials)
assert project_id is None
assert credentials.scopes == ["https://www.google.com/calendar/feeds"]


def test_load_credentials_from_file_service_account():
credentials, project_id = _default.load_credentials_from_file(SERVICE_ACCOUNT_FILE)
assert isinstance(credentials, service_account.Credentials)
assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"]


def test_load_credentials_from_file_service_account_with_scopes():
credentials, project_id = _default.load_credentials_from_file(
SERVICE_ACCOUNT_FILE, scopes=["https://www.google.com/calendar/feeds"]
)
assert isinstance(credentials, service_account.Credentials)
assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"]
assert credentials.scopes == ["https://www.google.com/calendar/feeds"]


def test__load_credentials_from_file_service_account_bad_format(tmpdir):
def test_load_credentials_from_file_service_account_bad_format(tmpdir):
filename = tmpdir.join("serivce_account_bad.json")
filename.write(json.dumps({"type": "service_account"}))

with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
_default._load_credentials_from_file(str(filename))
_default.load_credentials_from_file(str(filename))

assert excinfo.match(r"Failed to load service account")
assert excinfo.match(r"missing fields")
Expand Down