Skip to content

Commit

Permalink
Clean up and add tests for authenticate_duo
Browse files Browse the repository at this point in the history
  • Loading branch information
pcmxgti committed Oct 30, 2023
1 parent b3d8378 commit f5df5f6
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 20 deletions.
54 changes: 51 additions & 3 deletions tests/unit/test_duo.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,54 @@ def test_duo_factor_callback(mocker):
assert err.value.code == 2


def test_authenticate_duo():
"""TODO: Implement test."""
pass
def test_authenticate_duo(mocker):
"""Test end to end authentication."""
from tokendito.duo import authenticate_duo

mocker.patch(
"tokendito.duo.get_duo_sid",
return_value=(
{
"sid": "pytest",
"host": "pytest",
"state_token": "pytest",
"factor_id": "pytest",
"okta_callback_url": "pytest",
},
"pytest",
),
)
# We mock a lot of functions here, but we're really just testing that the data can flow,
# and that it can be parsed correctly to be sent to the API endpoint.
mocker.patch("tokendito.duo.get_duo_devices", return_value=[{"device": "pytest - device"}])
mocker.patch("tokendito.user.select_preferred_mfa_index", return_value=0)
mocker.patch("tokendito.user.input", return_value="0123456")
mocker.patch("tokendito.duo.duo_mfa_challenge", return_value="txid_pytest")
mocker.patch("tokendito.duo.duo_mfa_verify", return_value={"result_url": "/pytest_result_url"})
mocker.patch("tokendito.duo.duo_api_post", return_value=None)
mocker.patch("tokendito.duo.duo_factor_callback", return_value="pytest_cookie:pytest_tile_sig")
selected_okta_factor = {
"_embedded": {
"factor": {
"_embedded": {
"verification": {
"_links": {
"complete": {"href": "http://test.okta.href"},
"script": {"href": "python-v3.7"},
},
"signature": "fdsafdsa:fdsfdfds:fdsfdsfds",
"host": "test_host",
}
},
"id": 1234,
}
},
"stateToken": 12345,
}

res = authenticate_duo(selected_okta_factor)
assert {
"id": "pytest",
"sig_response": "pytest_cookie:pytest_tile_sig",
"stateToken": "pytest",
} == res
12 changes: 2 additions & 10 deletions tests/unit/test_okta.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,12 @@ def test_mfa_provider_type(
mocker.patch("tokendito.duo.duo_api_post", return_value=None)

payload = {"x": "y", "t": "z"}
callback_url = "https://www.acme.org"
selected_mfa_option = 1
mfa_challenge_url = 1
primary_auth = 1
pytest_config = Config()

mocker.patch(
"tokendito.duo.authenticate_duo",
return_value=(payload, sample_headers, callback_url),
)
mocker.patch("tokendito.duo.authenticate_duo", return_value=payload)
mocker.patch("tokendito.okta.push_approval", return_value={"sessionToken": session_token})
mocker.patch("tokendito.okta.totp_approval", return_value={"sessionToken": session_token})

Expand All @@ -128,7 +124,6 @@ def test_bad_mfa_provider_type(mocker, sample_headers):

pytest_config = Config()
payload = {"x": "y", "t": "z"}
callback_url = "https://www.acme.org"
selected_mfa_option = 1
mfa_challenge_url = 1
primary_auth = 1
Expand All @@ -140,10 +135,7 @@ def test_bad_mfa_provider_type(mocker, sample_headers):
mock_response = Mock()
mock_response.json.return_value = mfa_verify

mocker.patch(
"tokendito.duo.authenticate_duo",
return_value=(payload, sample_headers, callback_url),
)
mocker.patch("tokendito.duo.authenticate_duo", return_value=payload)
mocker.patch.object(HTTP_client, "post", return_value=mock_response)
mocker.patch("tokendito.okta.totp_approval", return_value=mfa_verify)

Expand Down
10 changes: 5 additions & 5 deletions tokendito/duo.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def get_duo_sid(duo_info):
logger.error(f"There was an error getting your SID. Please try again: {sid_error}")
sys.exit(2)

return duo_info, duo_auth_response
return (duo_info, duo_auth_response)


def get_duo_devices(duo_auth):
Expand Down Expand Up @@ -304,11 +304,10 @@ def authenticate_duo(selected_okta_factor):
:param selected_okta_factor: Duo factor information retrieved from Okta.
:return payload: required payload for Okta callback
:return headers: required headers for Okta callback
"""
duo_info = prepare_duo_info(selected_okta_factor)
# Collect devices, factors, auth params for Duo
duo_info, duo_auth_response = get_duo_sid(duo_info)
(duo_info, duo_auth_response) = get_duo_sid(duo_info)
factor_options = get_duo_devices(duo_auth_response)
mfa_index = user.select_preferred_mfa_index(
factor_options, factor_key="factor", subfactor_key="device"
Expand All @@ -330,6 +329,7 @@ def authenticate_duo(selected_okta_factor):
"sig_response": sig_response,
"stateToken": duo_info["state_token"],
}
headers = {"content-type": "application/json", "accept": "application/json"}

return payload, headers, duo_info["okta_callback_url"]
# Send Okta callback and return payload
duo_api_post(duo_info["okta_callback_url"], payload=payload)
return payload
4 changes: 2 additions & 2 deletions tokendito/okta.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,8 @@ def mfa_provider_type(
factor_type = selected_factor.get("_embedded", {}).get("factor", {}).get("factorType", None)

if mfa_provider == "DUO":
payload, headers, callback_url = duo.authenticate_duo(selected_factor)
duo.duo_api_post(callback_url, payload=payload)
mfa_verify = duo.authenticate_duo(selected_factor)
headers = {"content-type": "application/json", "accept": "application/json"}
mfa_verify = HTTP_client.post(
mfa_challenge_url, json=payload, headers=headers, return_json=True
)
Expand Down

0 comments on commit f5df5f6

Please sign in to comment.