aboutsummaryrefslogtreecommitdiff
path: root/tests_async
diff options
context:
space:
mode:
authorarithmetic1728 <58957152+arithmetic1728@users.noreply.github.com>2021-04-23 15:27:02 -0700
committerGitHub <noreply@github.com>2021-04-23 15:27:02 -0700
commit9e1082366d113286bc063051fd76b4799791d943 (patch)
treecfd8d1643f785cfdb0bcf860d85eca228b094780 /tests_async
parent36e6f0feb41e901effc854a4f8c907deb9998f21 (diff)
downloadgoogle-auth-library-python-9e1082366d113286bc063051fd76b4799791d943.tar.gz
feat: add reauth support to async user credentials (#738)
Diffstat (limited to 'tests_async')
-rw-r--r--tests_async/oauth2/test__client_async.py67
-rw-r--r--tests_async/oauth2/test_credentials_async.py36
-rw-r--r--tests_async/oauth2/test_reauth_async.py328
3 files changed, 392 insertions, 39 deletions
diff --git a/tests_async/oauth2/test__client_async.py b/tests_async/oauth2/test__client_async.py
index 458937a..6e48c45 100644
--- a/tests_async/oauth2/test__client_async.py
+++ b/tests_async/oauth2/test__client_async.py
@@ -29,34 +29,6 @@ from google.oauth2 import _client_async as _client
from tests.oauth2 import test__client as test_client
-def test__handle_error_response():
- response_data = json.dumps({"error": "help", "error_description": "I'm alive"})
-
- with pytest.raises(exceptions.RefreshError) as excinfo:
- _client._handle_error_response(response_data)
-
- assert excinfo.match(r"help: I\'m alive")
-
-
-def test__handle_error_response_non_json():
- response_data = "Help, I'm alive"
-
- with pytest.raises(exceptions.RefreshError) as excinfo:
- _client._handle_error_response(response_data)
-
- assert excinfo.match(r"Help, I\'m alive")
-
-
-@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min)
-def test__parse_expiry(unused_utcnow):
- result = _client._parse_expiry({"expires_in": 500})
- assert result == datetime.datetime.min + datetime.timedelta(seconds=500)
-
-
-def test__parse_expiry_none():
- assert _client._parse_expiry({}) is None
-
-
def make_request(response_data, status=http_client.OK):
response = mock.AsyncMock(spec=["transport.Response"])
response.status = status
@@ -82,7 +54,7 @@ async def test__token_endpoint_request():
request.assert_called_with(
method="POST",
url="http://example.com",
- headers={"content-type": "application/x-www-form-urlencoded"},
+ headers={"Content-Type": "application/x-www-form-urlencoded"},
body="test=params".encode("utf-8"),
)
@@ -91,6 +63,35 @@ async def test__token_endpoint_request():
@pytest.mark.asyncio
+async def test__token_endpoint_request_json():
+
+ request = make_request({"test": "response"})
+ access_token = "access_token"
+
+ result = await _client._token_endpoint_request(
+ request,
+ "http://example.com",
+ {"test": "params"},
+ access_token=access_token,
+ use_json=True,
+ )
+
+ # Check request call
+ request.assert_called_with(
+ method="POST",
+ url="http://example.com",
+ headers={
+ "Content-Type": "application/json",
+ "Authorization": "Bearer access_token",
+ },
+ body=b'{"test": "params"}',
+ )
+
+ # Check result
+ assert result == {"test": "response"}
+
+
+@pytest.mark.asyncio
async def test__token_endpoint_request_error():
request = make_request({}, status=http_client.BAD_REQUEST)
@@ -218,7 +219,12 @@ async def test_refresh_grant(unused_utcnow):
)
token, refresh_token, expiry, extra_data = await _client.refresh_grant(
- request, "http://example.com", "refresh_token", "client_id", "client_secret"
+ request,
+ "http://example.com",
+ "refresh_token",
+ "client_id",
+ "client_secret",
+ rapt_token="rapt_token",
)
# Check request call
@@ -229,6 +235,7 @@ async def test_refresh_grant(unused_utcnow):
"refresh_token": "refresh_token",
"client_id": "client_id",
"client_secret": "client_secret",
+ "rapt": "rapt_token",
},
)
diff --git a/tests_async/oauth2/test_credentials_async.py b/tests_async/oauth2/test_credentials_async.py
index 5c883d6..99cf16f 100644
--- a/tests_async/oauth2/test_credentials_async.py
+++ b/tests_async/oauth2/test_credentials_async.py
@@ -58,7 +58,7 @@ class TestCredentials:
assert credentials.client_id == self.CLIENT_ID
assert credentials.client_secret == self.CLIENT_SECRET
- @mock.patch("google.oauth2._client_async.refresh_grant", autospec=True)
+ @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True)
@mock.patch(
"google.auth._helpers.utcnow",
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
@@ -68,6 +68,7 @@ class TestCredentials:
token = "token"
expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
grant_response = {"id_token": mock.sentinel.id_token}
+ rapt_token = "rapt_token"
refresh_grant.return_value = (
# Access token
token,
@@ -77,6 +78,8 @@ class TestCredentials:
expiry,
# Extra data
grant_response,
+ # Rapt token
+ rapt_token,
)
request = mock.AsyncMock(spec=["transport.Request"])
@@ -93,12 +96,14 @@ class TestCredentials:
self.CLIENT_ID,
self.CLIENT_SECRET,
None,
+ None,
)
# Check that the credentials have the token and expiry
assert creds.token == token
assert creds.expiry == expiry
assert creds.id_token == mock.sentinel.id_token
+ assert creds.rapt_token == rapt_token
# Check that the credentials are valid (have a token and are not
# expired)
@@ -114,7 +119,7 @@ class TestCredentials:
request.assert_not_called()
- @mock.patch("google.oauth2._client_async.refresh_grant", autospec=True)
+ @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True)
@mock.patch(
"google.auth._helpers.utcnow",
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
@@ -127,6 +132,7 @@ class TestCredentials:
token = "token"
expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
grant_response = {"id_token": mock.sentinel.id_token}
+ rapt_token = "rapt_token"
refresh_grant.return_value = (
# Access token
token,
@@ -136,6 +142,8 @@ class TestCredentials:
expiry,
# Extra data
grant_response,
+ # Rapt token
+ rapt_token,
)
request = mock.AsyncMock(spec=["transport.Request"])
@@ -146,6 +154,7 @@ class TestCredentials:
client_id=self.CLIENT_ID,
client_secret=self.CLIENT_SECRET,
scopes=scopes,
+ rapt_token="old_rapt_token",
)
# Refresh credentials
@@ -159,6 +168,7 @@ class TestCredentials:
self.CLIENT_ID,
self.CLIENT_SECRET,
scopes,
+ "old_rapt_token",
)
# Check that the credentials have the token and expiry
@@ -166,12 +176,13 @@ class TestCredentials:
assert creds.expiry == expiry
assert creds.id_token == mock.sentinel.id_token
assert creds.has_scopes(scopes)
+ assert creds.rapt_token == rapt_token
# Check that the credentials are valid (have a token and are not
# expired.)
assert creds.valid
- @mock.patch("google.oauth2._client_async.refresh_grant", autospec=True)
+ @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True)
@mock.patch(
"google.auth._helpers.utcnow",
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
@@ -183,10 +194,8 @@ class TestCredentials:
scopes = ["email", "profile"]
token = "token"
expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
- grant_response = {
- "id_token": mock.sentinel.id_token,
- "scopes": " ".join(scopes),
- }
+ grant_response = {"id_token": mock.sentinel.id_token, "scope": " ".join(scopes)}
+ rapt_token = "rapt_token"
refresh_grant.return_value = (
# Access token
token,
@@ -196,6 +205,8 @@ class TestCredentials:
expiry,
# Extra data
grant_response,
+ # Rapt token
+ rapt_token,
)
request = mock.AsyncMock(spec=["transport.Request"])
@@ -219,6 +230,7 @@ class TestCredentials:
self.CLIENT_ID,
self.CLIENT_SECRET,
scopes,
+ None,
)
# Check that the credentials have the token and expiry
@@ -226,12 +238,13 @@ class TestCredentials:
assert creds.expiry == expiry
assert creds.id_token == mock.sentinel.id_token
assert creds.has_scopes(scopes)
+ assert creds.rapt_token == rapt_token
# Check that the credentials are valid (have a token and are not
# expired.)
assert creds.valid
- @mock.patch("google.oauth2._client_async.refresh_grant", autospec=True)
+ @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True)
@mock.patch(
"google.auth._helpers.utcnow",
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
@@ -246,8 +259,9 @@ class TestCredentials:
expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
grant_response = {
"id_token": mock.sentinel.id_token,
- "scopes": " ".join(scopes_returned),
+ "scope": " ".join(scopes_returned),
}
+ rapt_token = "rapt_token"
refresh_grant.return_value = (
# Access token
token,
@@ -257,6 +271,8 @@ class TestCredentials:
expiry,
# Extra data
grant_response,
+ # Rapt token
+ rapt_token,
)
request = mock.AsyncMock(spec=["transport.Request"])
@@ -267,6 +283,7 @@ class TestCredentials:
client_id=self.CLIENT_ID,
client_secret=self.CLIENT_SECRET,
scopes=scopes,
+ rapt_token=None,
)
# Refresh credentials
@@ -283,6 +300,7 @@ class TestCredentials:
self.CLIENT_ID,
self.CLIENT_SECRET,
scopes,
+ None,
)
# Check that the credentials have the token and expiry
diff --git a/tests_async/oauth2/test_reauth_async.py b/tests_async/oauth2/test_reauth_async.py
new file mode 100644
index 0000000..f144d89
--- /dev/null
+++ b/tests_async/oauth2/test_reauth_async.py
@@ -0,0 +1,328 @@
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import copy
+
+import mock
+import pytest
+
+from google.auth import exceptions
+from google.oauth2 import _reauth_async
+from google.oauth2 import reauth
+
+
+MOCK_REQUEST = mock.AsyncMock(spec=["transport.Request"])
+CHALLENGES_RESPONSE_TEMPLATE = {
+ "status": "CHALLENGE_REQUIRED",
+ "sessionId": "123",
+ "challenges": [
+ {
+ "status": "READY",
+ "challengeId": 1,
+ "challengeType": "PASSWORD",
+ "securityKey": {},
+ }
+ ],
+}
+CHALLENGES_RESPONSE_AUTHENTICATED = {
+ "status": "AUTHENTICATED",
+ "sessionId": "123",
+ "encodedProofOfReauthToken": "new_rapt_token",
+}
+
+
+class MockChallenge(object):
+ def __init__(self, name, locally_eligible, challenge_input):
+ self.name = name
+ self.is_locally_eligible = locally_eligible
+ self.challenge_input = challenge_input
+
+ def obtain_challenge_input(self, metadata):
+ return self.challenge_input
+
+
+@pytest.mark.asyncio
+async def test__get_challenges():
+ with mock.patch(
+ "google.oauth2._client_async._token_endpoint_request"
+ ) as mock_token_endpoint_request:
+ await _reauth_async._get_challenges(MOCK_REQUEST, ["SAML"], "token")
+ mock_token_endpoint_request.assert_called_with(
+ MOCK_REQUEST,
+ reauth._REAUTH_API + ":start",
+ {"supportedChallengeTypes": ["SAML"]},
+ access_token="token",
+ use_json=True,
+ )
+
+
+@pytest.mark.asyncio
+async def test__get_challenges_with_scopes():
+ with mock.patch(
+ "google.oauth2._client_async._token_endpoint_request"
+ ) as mock_token_endpoint_request:
+ await _reauth_async._get_challenges(
+ MOCK_REQUEST, ["SAML"], "token", requested_scopes=["scope"]
+ )
+ mock_token_endpoint_request.assert_called_with(
+ MOCK_REQUEST,
+ reauth._REAUTH_API + ":start",
+ {
+ "supportedChallengeTypes": ["SAML"],
+ "oauthScopesForDomainPolicyLookup": ["scope"],
+ },
+ access_token="token",
+ use_json=True,
+ )
+
+
+@pytest.mark.asyncio
+async def test__send_challenge_result():
+ with mock.patch(
+ "google.oauth2._client_async._token_endpoint_request"
+ ) as mock_token_endpoint_request:
+ await _reauth_async._send_challenge_result(
+ MOCK_REQUEST, "123", "1", {"credential": "password"}, "token"
+ )
+ mock_token_endpoint_request.assert_called_with(
+ MOCK_REQUEST,
+ reauth._REAUTH_API + "/123:continue",
+ {
+ "sessionId": "123",
+ "challengeId": "1",
+ "action": "RESPOND",
+ "proposalResponse": {"credential": "password"},
+ },
+ access_token="token",
+ use_json=True,
+ )
+
+
+@pytest.mark.asyncio
+async def test__run_next_challenge_not_ready():
+ challenges_response = copy.deepcopy(CHALLENGES_RESPONSE_TEMPLATE)
+ challenges_response["challenges"][0]["status"] = "STATUS_UNSPECIFIED"
+ assert (
+ await _reauth_async._run_next_challenge(
+ challenges_response, MOCK_REQUEST, "token"
+ )
+ is None
+ )
+
+
+@pytest.mark.asyncio
+async def test__run_next_challenge_not_supported():
+ challenges_response = copy.deepcopy(CHALLENGES_RESPONSE_TEMPLATE)
+ challenges_response["challenges"][0]["challengeType"] = "CHALLENGE_TYPE_UNSPECIFIED"
+ with pytest.raises(exceptions.ReauthFailError) as excinfo:
+ await _reauth_async._run_next_challenge(
+ challenges_response, MOCK_REQUEST, "token"
+ )
+ assert excinfo.match(r"Unsupported challenge type CHALLENGE_TYPE_UNSPECIFIED")
+
+
+@pytest.mark.asyncio
+async def test__run_next_challenge_not_locally_eligible():
+ mock_challenge = MockChallenge("PASSWORD", False, "challenge_input")
+ with mock.patch(
+ "google.oauth2.challenges.AVAILABLE_CHALLENGES", {"PASSWORD": mock_challenge}
+ ):
+ with pytest.raises(exceptions.ReauthFailError) as excinfo:
+ await _reauth_async._run_next_challenge(
+ CHALLENGES_RESPONSE_TEMPLATE, MOCK_REQUEST, "token"
+ )
+ assert excinfo.match(r"Challenge PASSWORD is not locally eligible")
+
+
+@pytest.mark.asyncio
+async def test__run_next_challenge_no_challenge_input():
+ mock_challenge = MockChallenge("PASSWORD", True, None)
+ with mock.patch(
+ "google.oauth2.challenges.AVAILABLE_CHALLENGES", {"PASSWORD": mock_challenge}
+ ):
+ assert (
+ await _reauth_async._run_next_challenge(
+ CHALLENGES_RESPONSE_TEMPLATE, MOCK_REQUEST, "token"
+ )
+ is None
+ )
+
+
+@pytest.mark.asyncio
+async def test__run_next_challenge_success():
+ mock_challenge = MockChallenge("PASSWORD", True, {"credential": "password"})
+ with mock.patch(
+ "google.oauth2.challenges.AVAILABLE_CHALLENGES", {"PASSWORD": mock_challenge}
+ ):
+ with mock.patch(
+ "google.oauth2._reauth_async._send_challenge_result"
+ ) as mock_send_challenge_result:
+ await _reauth_async._run_next_challenge(
+ CHALLENGES_RESPONSE_TEMPLATE, MOCK_REQUEST, "token"
+ )
+ mock_send_challenge_result.assert_called_with(
+ MOCK_REQUEST, "123", 1, {"credential": "password"}, "token"
+ )
+
+
+@pytest.mark.asyncio
+async def test__obtain_rapt_authenticated():
+ with mock.patch(
+ "google.oauth2._reauth_async._get_challenges",
+ return_value=CHALLENGES_RESPONSE_AUTHENTICATED,
+ ):
+ new_rapt_token = await _reauth_async._obtain_rapt(MOCK_REQUEST, "token", None)
+ assert new_rapt_token == "new_rapt_token"
+
+
+@pytest.mark.asyncio
+async def test__obtain_rapt_authenticated_after_run_next_challenge():
+ with mock.patch(
+ "google.oauth2._reauth_async._get_challenges",
+ return_value=CHALLENGES_RESPONSE_TEMPLATE,
+ ):
+ with mock.patch(
+ "google.oauth2._reauth_async._run_next_challenge",
+ side_effect=[
+ CHALLENGES_RESPONSE_TEMPLATE,
+ CHALLENGES_RESPONSE_AUTHENTICATED,
+ ],
+ ):
+ with mock.patch("google.oauth2.reauth.is_interactive", return_value=True):
+ assert (
+ await _reauth_async._obtain_rapt(MOCK_REQUEST, "token", None)
+ == "new_rapt_token"
+ )
+
+
+@pytest.mark.asyncio
+async def test__obtain_rapt_unsupported_status():
+ challenges_response = copy.deepcopy(CHALLENGES_RESPONSE_TEMPLATE)
+ challenges_response["status"] = "STATUS_UNSPECIFIED"
+ with mock.patch(
+ "google.oauth2._reauth_async._get_challenges", return_value=challenges_response
+ ):
+ with pytest.raises(exceptions.ReauthFailError) as excinfo:
+ await _reauth_async._obtain_rapt(MOCK_REQUEST, "token", None)
+ assert excinfo.match(r"API error: STATUS_UNSPECIFIED")
+
+
+@pytest.mark.asyncio
+async def test__obtain_rapt_not_interactive():
+ with mock.patch(
+ "google.oauth2._reauth_async._get_challenges",
+ return_value=CHALLENGES_RESPONSE_TEMPLATE,
+ ):
+ with mock.patch("google.oauth2.reauth.is_interactive", return_value=False):
+ with pytest.raises(exceptions.ReauthFailError) as excinfo:
+ await _reauth_async._obtain_rapt(MOCK_REQUEST, "token", None)
+ assert excinfo.match(r"not in an interactive session")
+
+
+@pytest.mark.asyncio
+async def test__obtain_rapt_not_authenticated():
+ with mock.patch(
+ "google.oauth2._reauth_async._get_challenges",
+ return_value=CHALLENGES_RESPONSE_TEMPLATE,
+ ):
+ with mock.patch("google.oauth2.reauth.RUN_CHALLENGE_RETRY_LIMIT", 0):
+ with pytest.raises(exceptions.ReauthFailError) as excinfo:
+ await _reauth_async._obtain_rapt(MOCK_REQUEST, "token", None)
+ assert excinfo.match(r"Reauthentication failed")
+
+
+@pytest.mark.asyncio
+async def test_get_rapt_token():
+ with mock.patch(
+ "google.oauth2._client_async.refresh_grant",
+ return_value=("token", None, None, None),
+ ) as mock_refresh_grant:
+ with mock.patch(
+ "google.oauth2._reauth_async._obtain_rapt", return_value="new_rapt_token"
+ ) as mock_obtain_rapt:
+ assert (
+ await _reauth_async.get_rapt_token(
+ MOCK_REQUEST,
+ "client_id",
+ "client_secret",
+ "refresh_token",
+ "token_uri",
+ )
+ == "new_rapt_token"
+ )
+ mock_refresh_grant.assert_called_with(
+ request=MOCK_REQUEST,
+ client_id="client_id",
+ client_secret="client_secret",
+ refresh_token="refresh_token",
+ token_uri="token_uri",
+ scopes=[reauth._REAUTH_SCOPE],
+ )
+ mock_obtain_rapt.assert_called_with(
+ MOCK_REQUEST, "token", requested_scopes=None
+ )
+
+
+@pytest.mark.asyncio
+async def test_refresh_grant_failed():
+ with mock.patch(
+ "google.oauth2._client_async._token_endpoint_request_no_throw"
+ ) as mock_token_request:
+ mock_token_request.return_value = (False, {"error": "Bad request"})
+ with pytest.raises(exceptions.RefreshError) as excinfo:
+ await _reauth_async.refresh_grant(
+ MOCK_REQUEST,
+ "token_uri",
+ "refresh_token",
+ "client_id",
+ "client_secret",
+ scopes=["foo", "bar"],
+ rapt_token="rapt_token",
+ )
+ assert excinfo.match(r"Bad request")
+ mock_token_request.assert_called_with(
+ MOCK_REQUEST,
+ "token_uri",
+ {
+ "grant_type": "refresh_token",
+ "client_id": "client_id",
+ "client_secret": "client_secret",
+ "refresh_token": "refresh_token",
+ "scope": "foo bar",
+ "rapt": "rapt_token",
+ },
+ )
+
+
+@pytest.mark.asyncio
+async def test_refresh_grant_success():
+ with mock.patch(
+ "google.oauth2._client_async._token_endpoint_request_no_throw"
+ ) as mock_token_request:
+ mock_token_request.side_effect = [
+ (False, {"error": "invalid_grant", "error_subtype": "rapt_required"}),
+ (True, {"access_token": "access_token"}),
+ ]
+ with mock.patch(
+ "google.oauth2._reauth_async.get_rapt_token", return_value="new_rapt_token"
+ ):
+ assert await _reauth_async.refresh_grant(
+ MOCK_REQUEST, "token_uri", "refresh_token", "client_id", "client_secret"
+ ) == (
+ "access_token",
+ "refresh_token",
+ None,
+ {"access_token": "access_token"},
+ "new_rapt_token",
+ )