Skip to content

Commit

Permalink
Disable Pluggable Auth for Python 2.*
Browse files Browse the repository at this point in the history
  • Loading branch information
Chuan Ren committed May 27, 2022
1 parent 52f6950 commit 1afbf86
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 62 deletions.
24 changes: 17 additions & 7 deletions google/auth/pluggable.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import json
import os
import subprocess
import sys
import time

from google.auth import _helpers
Expand Down Expand Up @@ -211,20 +212,29 @@ def retrieve_subject_token(self, request):
"GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE"
] = self._credential_source_executable_output_file

if sys.version_info < (3, 0):
raise exceptions.RefreshError(
"Pluggable auth is only supported for python 3.6+"
)

try:
result = subprocess.check_output(
result = subprocess.run(
self._credential_source_executable_command.split(),
timeout=self._credential_source_executable_timeout_millis / 1000,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
except subprocess.CalledProcessError as e:
raise exceptions.RefreshError(
"Executable exited with non-zero return code {}. Error: {}".format(
e.returncode, e.output
if result.returncode != 0:
raise exceptions.RefreshError(
"Executable exited with non-zero return code {}. Error: {}".format(
result.returncode, result.stdout
)
)
)
except Exception:
raise
else:
try:
data = result.decode("utf-8")
data = result.stdout.decode("utf-8")
response = json.loads(data)
subject_token = self._parse_subject_token(response)
except Exception:
Expand Down
3 changes: 2 additions & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
]


@nox.session(python="3.8")
@nox.session()
def lint(session):
session.install(
"flake8", "flake8-import-order", "docutils", CLICK_VERSION, BLACK_VERSION
Expand Down Expand Up @@ -117,6 +117,7 @@ def unit_prev_versions(session):
"--cov=google.auth",
"--cov=google.oauth2",
"--cov=tests",
"--ignore=tests/test_pluggable.py", # Pluggable auth only support 3.6+ for now.
"tests",
)

Expand Down
168 changes: 114 additions & 54 deletions tests/test_pluggable.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,14 @@ def test_info_with_credential_source(self):
)
def test_retrieve_subject_token_oidc_id_token(self):
with mock.patch(
"subprocess.check_output",
return_value=json.dumps(
self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN
).encode("UTF-8"),
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(
self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN
).encode("UTF-8"),
returncode=0,
),
):
credentials = self.make_pluggable(
audience=AUDIENCE,
Expand All @@ -287,10 +291,14 @@ def test_retrieve_subject_token_oidc_id_token(self):
@mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"})
def test_retrieve_subject_token_oidc_jwt(self):
with mock.patch(
"subprocess.check_output",
return_value=json.dumps(
self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_JWT
).encode("UTF-8"),
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_JWT).encode(
"UTF-8"
),
returncode=0,
),
):
credentials = self.make_pluggable(
audience=AUDIENCE,
Expand All @@ -305,9 +313,13 @@ def test_retrieve_subject_token_oidc_jwt(self):
@mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"})
def test_retrieve_subject_token_saml(self):
with mock.patch(
"subprocess.check_output",
return_value=json.dumps(self.EXECUTABLE_SUCCESSFUL_SAML_RESPONSE).encode(
"UTF-8"
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(self.EXECUTABLE_SUCCESSFUL_SAML_RESPONSE).encode(
"UTF-8"
),
returncode=0,
),
):
credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE)
Expand All @@ -319,8 +331,12 @@ def test_retrieve_subject_token_saml(self):
@mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"})
def test_retrieve_subject_token_failed(self):
with mock.patch(
"subprocess.check_output",
return_value=json.dumps(self.EXECUTABLE_FAILED_RESPONSE).encode("UTF-8"),
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(self.EXECUTABLE_FAILED_RESPONSE).encode("UTF-8"),
returncode=0,
),
):
credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE)

Expand All @@ -334,10 +350,14 @@ def test_retrieve_subject_token_failed(self):
@mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "0"})
def test_retrieve_subject_token_not_allowd(self):
with mock.patch(
"subprocess.check_output",
return_value=json.dumps(
self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN
).encode("UTF-8"),
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(
self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN
).encode("UTF-8"),
returncode=0,
),
):
credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE)

Expand All @@ -357,10 +377,14 @@ def test_retrieve_subject_token_invalid_version(self):
}

with mock.patch(
"subprocess.check_output",
return_value=json.dumps(
EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_VERSION_2
).encode("UTF-8"),
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_VERSION_2).encode(
"UTF-8"
),
returncode=0,
),
):
credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE)

Expand All @@ -380,9 +404,13 @@ def test_retrieve_subject_token_expired_token(self):
}

with mock.patch(
"subprocess.check_output",
return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_EXPIRED).encode(
"UTF-8"
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_EXPIRED).encode(
"UTF-8"
),
returncode=0,
),
):
credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE)
Expand Down Expand Up @@ -420,10 +448,14 @@ def test_retrieve_subject_token_no_file_cache(self):
ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE}

with mock.patch(
"subprocess.check_output",
return_value=json.dumps(
self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN
).encode("UTF-8"),
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(
self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN
).encode("UTF-8"),
returncode=0,
),
):
credentials = self.make_pluggable(
credential_source=ACTUAL_CREDENTIAL_SOURCE
Expand Down Expand Up @@ -480,10 +512,14 @@ def test_retrieve_subject_token_file_cache_refresh_error_retry(self):
json.dump(ACTUAL_EXECUTABLE_RESPONSE, output_file)

with mock.patch(
"subprocess.check_output",
return_value=json.dumps(
self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN
).encode("UTF-8"),
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(
self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN
).encode("UTF-8"),
returncode=0,
),
):
credentials = self.make_pluggable(
credential_source=ACTUAL_CREDENTIAL_SOURCE
Expand All @@ -506,9 +542,11 @@ def test_retrieve_subject_token_unsupported_token_type(self):
}

with mock.patch(
"subprocess.check_output",
return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode(
"UTF-8"
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode("UTF-8"),
returncode=0,
),
):
credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE)
Expand All @@ -528,9 +566,11 @@ def test_retrieve_subject_token_missing_version(self):
}

with mock.patch(
"subprocess.check_output",
return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode(
"UTF-8"
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode("UTF-8"),
returncode=0,
),
):
credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE)
Expand All @@ -552,9 +592,11 @@ def test_retrieve_subject_token_missing_success(self):
}

with mock.patch(
"subprocess.check_output",
return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode(
"UTF-8"
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode("UTF-8"),
returncode=0,
),
):
credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE)
Expand All @@ -571,9 +613,11 @@ def test_retrieve_subject_token_missing_error_code_message(self):
EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = {"version": 1, "success": False}

with mock.patch(
"subprocess.check_output",
return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode(
"UTF-8"
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode("UTF-8"),
returncode=0,
),
):
credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE)
Expand All @@ -595,9 +639,11 @@ def test_retrieve_subject_token_missing_expiration_time(self):
}

with mock.patch(
"subprocess.check_output",
return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode(
"UTF-8"
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode("UTF-8"),
returncode=0,
),
):
credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE)
Expand All @@ -619,9 +665,11 @@ def test_retrieve_subject_token_missing_token_type(self):
}

with mock.patch(
"subprocess.check_output",
return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode(
"UTF-8"
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[],
stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode("UTF-8"),
returncode=0,
),
):
credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE)
Expand Down Expand Up @@ -678,10 +726,12 @@ def test_credential_source_timeout_large(self):

@mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"})
def test_retrieve_subject_token_executable_fail(self):
with mock.patch("subprocess.check_output") as subprocess_mock:
subprocess_mock.side_effect = subprocess.CalledProcessError(
returncode=1, cmd=""
)
with mock.patch(
"subprocess.run",
return_value=subprocess.CompletedProcess(
args=[], stdout=None, returncode=1
),
):
credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE)

with pytest.raises(exceptions.RefreshError) as excinfo:
Expand All @@ -690,3 +740,13 @@ def test_retrieve_subject_token_executable_fail(self):
assert excinfo.match(
r"Executable exited with non-zero return code 1. Error: None"
)

@mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"})
def test_retrieve_subject_token_python_2(self):
with mock.patch("sys.version_info", (2, 7)):
credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE)

with pytest.raises(exceptions.RefreshError) as excinfo:
_ = credentials.retrieve_subject_token(None)

assert excinfo.match(r"Pluggable auth is only supported for python 3.6+")

0 comments on commit 1afbf86

Please sign in to comment.