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") correct_output = ( "\nSelect your preferred MFA method and press Enter:\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) ) mocker.patch("tokendito.helpers.collect_integer", return_value=1) select_preferred_mfa_index(mfa_options) captured = capsys.readouterr() assert captured.out == correct_output
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") for output in mfa_options: mocker.patch("tokendito.helpers.collect_integer", return_value=output) assert select_preferred_mfa_index(mfa_options) == output
def user_mfa_index(preset_mfa, available_mfas, mfa_options): """Get mfa method index in request. :param preset_mfa: preset mfa method from settings :param available_mfas: available mfa method ids :param mfa_options: available mfa methods """ logging.debug("Get mfa method index in request.") if preset_mfa is not None and preset_mfa in available_mfas: mfa_index = available_mfas.index(preset_mfa) else: mfa_index = helpers.select_preferred_mfa_index(mfa_options) return mfa_index
def authenticate_duo(selected_okta_factor): """Accomplish MFA via Duo. This is the main function that coordinates the Duo multifactor fetching, presentation, selection, challenge, and verification until making an Okta callback. :param selected_okta_factor: Duo factor information retrieved from Okta. :return payload: required payload for Okta callback :return headers: required headers for Okta callback """ try: duo_info = prepare_duo_info(selected_okta_factor) except KeyError as missing_key: logging.error( "There was an issue parsing the Okta factor." " Please try again. \n{}".format(missing_key) ) sys.exit(1) # Collect devices, factors, auth params for Duo duo_info, duo_auth_response = get_duo_sid(duo_info) factor_options = get_duo_devices(duo_auth_response) mfa_index = helpers.select_preferred_mfa_index( factor_options, factor_key="factor", subfactor_key="device" ) mfa_option = factor_options[mfa_index] logging.debug("Selected MFA is [{}]".format(mfa_option)) passcode = set_passcode(mfa_option) txid = duo_mfa_challenge(duo_info, mfa_option, passcode) verify_mfa = duo_mfa_verify(duo_info, txid) # Make factor callback to Duo sig_response = duo_factor_callback(duo_info, verify_mfa) # Prepare for Okta callback payload = helpers.prepare_payload( id=duo_info["factor_id"], sig_response=sig_response, stateToken=duo_info["state_token"], ) headers = {} headers["content-type"] = "application/json" headers["accept"] = "application/json" return payload, headers, duo_info["okta_callback_url"]
def user_mfa_challenge(headers, primary_auth): """Handle user mfa challenges. :param headers: headers what needs to be sent to api :param primary_auth: primary authentication :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: logging.error("Okta auth failed: " "Could not retrieve list of MFA methods") logging.debug("Error parsing response: {}".format( json.dumps(primary_auth))) sys.exit(1) mfa_setup_statuses = [ d["status"] for d in mfa_options if "status" in d and d["status"] != "ACTIVE" ] if len(mfa_setup_statuses) == len(mfa_options): logging.error("MFA not configured. " "Please enable MFA on your account and try again.") sys.exit(2) preset_mfa = settings.mfa_method available_mfas = [d["factorType"] for d in mfa_options] if preset_mfa is not None and preset_mfa in available_mfas: mfa_index = available_mfas.index(settings.mfa_method) else: logging.warning( "No MFA provided or provided MFA does not exist. [{}]".format( settings.mfa_method)) mfa_index = helpers.select_preferred_mfa_index(mfa_options) # time to challenge the mfa option selected_mfa_option = mfa_options[mfa_index] logging.debug("Selected MFA is [{}]".format(selected_mfa_option)) mfa_challenge_url = selected_mfa_option["_links"]["verify"]["href"] payload = helpers.prepare_payload( stateToken=primary_auth["stateToken"], factorType=selected_mfa_option["factorType"], provider=selected_mfa_option["provider"], profile=selected_mfa_option["profile"], ) selected_factor = okta_verify_api_method(mfa_challenge_url, payload, headers) mfa_provider = selected_factor["_embedded"]["factor"]["provider"].lower() logging.debug("MFA Challenge URL: [{}] headers: {}".format( mfa_challenge_url, headers)) if mfa_provider == "duo": payload, headers, callback_url = duo_helpers.authenticate_duo( selected_factor) okta_verify_api_method(callback_url, payload) payload.pop("id", "sig_response") mfa_verify = okta_verify_api_method(mfa_challenge_url, payload, headers) elif mfa_provider == "okta" or mfa_provider == "google": mfa_verify = user_mfa_options(selected_mfa_option, headers, mfa_challenge_url, payload, primary_auth) else: logging.error( "Sorry, the MFA provider '{}' is not yet supported." " Please retry with another option.".format(mfa_provider)) exit(1) return mfa_verify["sessionToken"]