コード例 #1
0
ファイル: unit_test.py プロジェクト: pcmxgti/tokendito
def test_prepare_payload():
    """Check if values passed return in a dictionary."""
    from tokendito import helpers

    assert helpers.prepare_payload(pytest_key="pytest_val") == {
        "pytest_key": "pytest_val"
    }
    assert helpers.prepare_payload(pytest_key=None) == {"pytest_key": None}
    assert helpers.prepare_payload(
        pytest_key1="pytest_val1", pytest_key2="pytest_val2"
    ) == {"pytest_key1": "pytest_val1", "pytest_key2": "pytest_val2"}
コード例 #2
0
def test_prepare_payload():
    """Check if values passed return in a dictionary."""
    from tokendito import helpers

    assert helpers.prepare_payload(pytest_key='pytest_val') == {
        'pytest_key': 'pytest_val'
    }
    assert helpers.prepare_payload(pytest_key=None) == {'pytest_key': None}
    assert helpers.prepare_payload(pytest_key1='pytest_val1',
                                   pytest_key2='pytest_val2') == {
                                       'pytest_key1': 'pytest_val1',
                                       'pytest_key2': 'pytest_val2'
                                   }
コード例 #3
0
def authenticate_user(okta_url, okta_username, okta_password):
    """Authenticate user with okta credential.

    :param okta_url: company specific URL of the okta
    :param okta_username: okta username
    :param okta_password: okta password
    :return: MFA session options

    """
    logging.debug("Authenticate user with okta credential [{} user {}]".format(
        okta_url, okta_username))
    headers = {
        "content-type": "application/json",
        "accept": "application/json"
    }
    payload = helpers.prepare_payload(username=okta_username,
                                      password=okta_password)

    primary_auth = okta_verify_api_method("{}/api/v1/authn".format(okta_url),
                                          payload, headers)
    logging.debug("Authenticate Okta header [{}] ".format(headers))

    session_token = user_mfa_challenge(headers, primary_auth)

    logging.info("User has been succesfully authenticated.")
    return session_token
コード例 #4
0
def user_mfa_options(selected_mfa_option, headers, mfa_challenge_url, payload,
                     primary_auth):
    """Handle user mfa options.

    :param selected_mfa_option: Selected MFA option (SMS, push, etc)
    :param headers: headers
    :param mfa_challenge_url: MFA challenge URL
    :param payload: payload
    :param primary_auth: Primary authentication method
    :return: payload data

    """
    logging.debug("Handle user MFA options")

    logging.debug("User MFA options selected: [{}]".format(
        selected_mfa_option["factorType"]))
    if selected_mfa_option["factorType"] == "push":
        return push_approval(headers, mfa_challenge_url, payload)

    if settings.mfa_response is None:
        logging.debug("Getting verification code from user.")
        print("Type verification code and press Enter")
        settings.mfa_response = helpers.get_input()

    # time to verify the mfa method
    payload = helpers.prepare_payload(stateToken=primary_auth["stateToken"],
                                      passCode=settings.mfa_response)
    mfa_verify = okta_verify_api_method(mfa_challenge_url, payload, headers)
    logging.debug("mfa_verify [{}]".format(json.dumps(mfa_verify)))

    return mfa_verify
コード例 #5
0
def get_duo_sid(duo_info):
    """Perform the initial Duo authentication request to obtain the SID.

    The SID is referenced throughout the authentication process for Duo.

    :param duo_info: dict response describing Duo factor in Okta.
    :return: duo_info with added SID.
    :return: duo_auth_response, contains html content listing available factors.
    """
    params = helpers.prepare_payload(
        tx=duo_info["tx"], v=duo_info["version"], parent=duo_info["parent"]
    )

    url = "https://{}/frame/web/v1/auth".format(duo_info["host"])
    logging.info(
        "Calling Duo {} with params {}".format(urlparse(url).path, params.keys())
    )
    duo_auth_response = duo_api_post(url, params=params)

    try:
        duo_auth_redirect = urlparse("{}".format(unquote(duo_auth_response.url))).query
        duo_info["sid"] = duo_auth_redirect.strip("sid=")
    except Exception as sid_error:
        logging.error(
            "There was an error getting your SID."
            "Please try again. \n{}".format(sid_error)
        )

    return duo_info, duo_auth_response
コード例 #6
0
def duo_mfa_challenge(duo_info, mfa_option, passcode):
    """Poke Duo to challenge the selected factor.

    After the user has selected their device and factor of choice,
    tell Duo to send a challenge. This is where the end user will receive
    a phone call or push.

    :param duo_info: dict of parameters for Duo
    :param mfa_option: the user's selected second factor.
    :return txid: Duo transaction ID used to track this auth attempt.
    """
    url = "https://{}/frame/prompt".format(duo_info["host"])
    device = mfa_option["device"].split(" - ")[0]
    mfa_data = helpers.prepare_payload(
        factor=mfa_option["factor"],
        device=device,
        sid=duo_info["sid"],
        out_of_date=False,
        days_out_of_date=0,
        days_to_block=None,
    )
    mfa_data["async"] = True  # async is a reserved keyword
    if passcode:
        mfa_data["passcode"] = passcode
    mfa_challenge = duo_api_post(url, payload=mfa_data)
    txid = parse_duo_mfa_challenge(mfa_challenge)

    logging.debug("Sent MFA Challenge and obtained Duo transaction ID.")
    return txid
コード例 #7
0
ファイル: okta_helpers.py プロジェクト: pcmxgti/tokendito
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")

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

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

    if available_mfas.count(preset_mfa) > 1:
        mfa_method = settings.mfa_method
        mfa_index = available_mfas.index(preset_mfa)
        provider = mfa_options[mfa_index]["provider"]
        mfa_id = mfa_options[mfa_index]["id"]

        logging.warning("\n\nMore than one method found with {}.\n"
                        "Defaulting to {} - {} - Id: {}.\n"
                        "This functionality will be deprecated in"
                        "the next major release.\n".format(
                            mfa_method, provider, mfa_method, mfa_id))

    mfa_index = user_mfa_index(preset_mfa, available_mfas, 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))
    mfa_session_token = mfa_provider_type(
        mfa_provider,
        selected_factor,
        mfa_challenge_url,
        primary_auth,
        selected_mfa_option,
        headers,
        payload,
    )

    return mfa_session_token
コード例 #8
0
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"]
コード例 #9
0
def duo_mfa_verify(duo_info, txid):
    """Verify MFA challenge completion.

    After the user has received the MFA challenge, query the Duo API
    until the challenge is completed.

    :param duo_info: dict of parameters for Duo.
    :param mfa_option: the user's selected second factor.
    :return txid: Duo transaction ID used to track this auth attempt.
    """
    url = "https://{}/frame/status".format(duo_info["host"])
    challenged_mfa = helpers.prepare_payload(txid=txid, sid=duo_info["sid"])
    challenge_result = None

    while True:
        logging.debug("Waiting for MFA challenge response")
        mfa_result = duo_api_post(url, payload=challenged_mfa)
        verify_mfa = get_mfa_response(mfa_result)
        challenge_result, challenge_reason = parse_challenge(
            verify_mfa, challenge_result
        )

        if challenge_result is None:
            continue
        elif challenge_result == "success":
            logging.debug("Successful MFA challenge received")
            break
        elif challenge_result == "failure":
            logging.critical(
                "MFA challenge has failed:"
                " {}. Please try again.".format(challenge_reason)
            )
            sys.exit(2)
        else:
            logging.debug(
                "MFA challenge result: {}"
                "Reason: {}\n\n".format(challenge_result, challenge_reason)
            )
        time.sleep(1)

    return verify_mfa
コード例 #10
0
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"]