Skip to content

Commit

Permalink
add tests for authentication without mfa (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
kudrinyaroslav authored Jul 7, 2021
1 parent c167291 commit 45577b9
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 136 deletions.
39 changes: 39 additions & 0 deletions tests/okta_response_simulation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# vim: set filetype=python ts=4 sw=4
# -*- coding: utf-8 -*-
"""simulation okta reponses."""

with_mfa = {
"status": "MFA_REQUIRED",
"_embedded": {
"user": {
"profile": {
"login": "[email protected]",
},
},
"factors": [
{
"id": "opfrar9yi4bKJNH2WEWQ0x8",
"factorType": "push",
"provider": "OKTA",
"profile": {"name": "Redmi 6 Pro"},
},
{
"id": "FfdskljfdsS1ljUT0r8",
"factorType": "token:software:totp",
"provider": "GOOGLE",
"profile": {"credentialId": "[email protected]"},
},
{
"id": "fdsfsd6ewREr8",
"factorType": "token:software:totp",
"provider": "OKTA",
"profile": {"credentialId": "[email protected]"},
},
],
},
}
no_mfa_no_session_token = {"status": "SUCCESS", "sessionToken": None}
no_mfa = {"status": "SUCCESS", "sessionToken": 345}
error_dict = {"errorCode": "E0000004"}
empty_dict = {}
no_auth_methods = {"status": "MFA_REQUIRED"}
109 changes: 0 additions & 109 deletions tests/samples.py

This file was deleted.

81 changes: 68 additions & 13 deletions tests/unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@

from future import standard_library
import pytest
from samples import primary_auth
import semver
from tokendito.settings import okta_status_dict

Expand Down Expand Up @@ -106,7 +105,22 @@ def invalid_settings():
@pytest.fixture
def sample_json_response():
"""Return a response from okta server."""
return primary_auth
from okta_response_simulation import no_mfa_no_session_token
from okta_response_simulation import no_mfa
from okta_response_simulation import error_dict
from okta_response_simulation import empty_dict
from okta_response_simulation import no_auth_methods
from okta_response_simulation import with_mfa

okta_fixture_data = {
"okta_response_no_auth_methods": no_auth_methods,
"okta_response_empty": empty_dict,
"okta_response_error": error_dict,
"okta_response_no_mfa": no_mfa,
"okta_response_no_mfa_no_session_token": no_mfa_no_session_token,
"okta_response_mfa": with_mfa,
}
return okta_fixture_data


@pytest.fixture
Expand Down Expand Up @@ -379,20 +393,48 @@ def test_process_ini_file(tmpdir, valid_settings, invalid_settings, mocker):


@pytest.mark.parametrize(
"status, session_token, expected",
[("SUCCESS", 123, 123), ("MFA_REQUIRED", 345, 345)],
"session_token, expected, mfa_availability",
[
(345, 345, "okta_response_no_auth_methods"),
(345, 345, "okta_response_mfa"),
(345, 345, "okta_response_no_auth_methods"),
(None, None, "okta_response_no_mfa_no_session_token"),
],
)
def test_user_session_token(status, session_token, expected, mocker, sample_headers):
def test_user_session_token(
sample_json_response,
session_token,
expected,
mocker,
sample_headers,
mfa_availability,
):
"""Test whether function return key on specific status."""
from tokendito.okta_helpers import user_session_token

primary_auth = {"status": status, "sessionToken": session_token}
primary_auth = sample_json_response[mfa_availability]

mocker.patch(
"tokendito.okta_helpers.user_mfa_challenge", return_value=session_token
)
assert user_session_token(primary_auth, sample_headers) == expected


def test_bad_user_session_token(sample_json_response, sample_headers, mocker):
"""Test whether function behave accordingly."""
from tokendito.okta_helpers import user_session_token

mocker.patch("tokendito.okta_helpers.login_error_code_parser", return_value=None)
okta_response_statuses = ["okta_response_error", "okta_response_empty"]

for response in okta_response_statuses:

primary_auth = sample_json_response[response]

with pytest.raises(SystemExit) as error:
assert user_session_token(primary_auth, sample_headers) == error


@pytest.mark.parametrize(
"mfa_provider, session_token, expected",
[("duo", 123, 123), ("okta", 345, 345), ("google", 456, 456)],
Expand Down Expand Up @@ -528,7 +570,8 @@ def test_user_mfa_index(preset_mfa, output, mocker, sample_json_response):
"""Test whether the function returns correct mfa method index."""
from tokendito.okta_helpers import user_mfa_index

primary_auth = sample_json_response()
primary_auth = sample_json_response["okta_response_mfa"]

mfa_options = primary_auth["_embedded"]["factors"]
available_mfas = [d["factorType"] for d in mfa_options]
mocker.patch("tokendito.helpers.select_preferred_mfa_index", return_value=1)
Expand All @@ -540,8 +583,8 @@ def test_select_preferred_mfa_index(mocker, sample_json_response):
"""Test whether the function returns index entered by user."""
from tokendito.helpers import select_preferred_mfa_index

primary_auth = sample_json_response()
mfa_options = primary_auth.get("_embedded").get("factors")
primary_auth = sample_json_response
mfa_options = primary_auth["okta_response_mfa"]["_embedded"]["factors"]
for output in mfa_options:
mocker.patch("tokendito.helpers.collect_integer", return_value=output)
assert select_preferred_mfa_index(mfa_options) == output
Expand All @@ -550,19 +593,19 @@ def test_select_preferred_mfa_index(mocker, sample_json_response):
@pytest.mark.parametrize(
"email",
[
("Firstname.Lastname@acme.org"),
("Token.Dito@acme.org"),
],
)
def test_select_preferred_mfa_index_output(email, capsys, mocker, sample_json_response):
"""Test whether the function gives correct output."""
from tokendito.helpers import select_preferred_mfa_index

primary_auth = sample_json_response(email=email)
mfa_options = primary_auth.get("_embedded").get("factors")
primary_auth = sample_json_response
mfa_options = primary_auth["okta_response_mfa"]["_embedded"]["factors"]

correct_output = (
"\nSelect your preferred MFA method and press Enter:\n"
"[0] OKTA push Redmi 6 Pro Id: opfrar9yi4bKJNH2WEWQ0x8\n"
"[0] OKTA push Redmi 6 Pro Id: opfrar9yi4bKJNH2WEWQ0x8\n"
"[1] GOOGLE token:software:totp {0} Id: FfdskljfdsS1ljUT0r8\n"
"[2] OKTA token:software:totp {0} Id: fdsfsd6ewREr8\n".format(email)
)
Expand All @@ -571,3 +614,15 @@ def test_select_preferred_mfa_index_output(email, capsys, mocker, sample_json_re
select_preferred_mfa_index(mfa_options)
captured = capsys.readouterr()
assert captured.out == correct_output


def test_bad_with_no_mfa_methods_user_mfa_challenge(
sample_headers, sample_json_response
):
"""Test whether okta response has mfa methods."""
from tokendito.okta_helpers import user_mfa_challenge

primary_auth = sample_json_response["okta_response_no_auth_methods"]

with pytest.raises(SystemExit) as error:
assert user_mfa_challenge(sample_headers, primary_auth) == error
2 changes: 1 addition & 1 deletion tokendito/__version__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# vim: set filetype=python ts=4 sw=4
# -*- coding: utf-8 -*-
"""tokendito version."""
__version__ = "1.2.0"
__version__ = "1.2.1"
__title__ = "tokendito"
__description__ = "Get AWS STS tokens from Okta SSO"
__long_description_content_type__ = "text/x-rst"
Expand Down
29 changes: 16 additions & 13 deletions tokendito/okta_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@ def okta_verify_api_method(mfa_challenge_url, payload, headers=None):
logging.debug("Received type of response: {}".format(type(response.text)))
response = response.text

if "errorCode" in response:
login_error_code_parser(response["errorCode"], settings.okta_status_dict)
sys.exit(1)

return response


Expand Down Expand Up @@ -87,16 +83,20 @@ def user_session_token(primary_auth, headers):
param primary_auth: Primary authentication
return session_token: Session Token from JSON response
"""
try:
status = primary_auth.get("errorCode", primary_auth["status"])
except KeyError:
logging.debug("Error parsing response: {}".format(json.dumps(primary_auth)))
logging.error("Okta auth failed: unknown status")
sys.exit(1)
status = None
if primary_auth.get("errorCode"):
status = primary_auth.get("errorCode")
else:
status = primary_auth.get("status")

if status == "SUCCESS":
session_token = primary_auth["sessionToken"]
session_token = primary_auth.get("sessionToken")
elif status == "MFA_REQUIRED":
session_token = user_mfa_challenge(headers, primary_auth)
elif status is None:
logging.debug("Error parsing response: {}".format(json.dumps(primary_auth)))
logging.error("Okta auth failed: unknown status")
sys.exit(1)
else:
login_error_code_parser(status, settings.okta_status_dict)
sys.exit(1)
Expand Down Expand Up @@ -126,7 +126,6 @@ def authenticate_user(okta_url, okta_username, okta_password):
logging.debug("Authenticate Okta header [{}] ".format(headers))

session_token = user_session_token(primary_auth, headers)

logging.info("User has been succesfully authenticated.")
return session_token

Expand Down Expand Up @@ -192,8 +191,12 @@ def user_mfa_challenge(headers, primary_auth):
:return: Okta MFA Session token after the successful entry of the code
"""
logging.debug("Handle user MFA challenges")
try:
mfa_options = primary_auth["_embedded"]["factors"]
except KeyError as error:
logging.error("There was a wrong response structure: \n{}".format(error))
sys.exit(1)

mfa_options = primary_auth["_embedded"]["factors"]
preset_mfa = settings.mfa_method

available_mfas = [d["factorType"] for d in mfa_options]
Expand Down

0 comments on commit 45577b9

Please sign in to comment.