func verifyDuoMfa()

in pkg/provider/shibboleth/shibboleth.go [174:375]


func verifyDuoMfa(oc *Client, duoHost string, parent string, tx string) (string, error) {
	// initiate duo mfa to get sid
	duoSubmitURL := fmt.Sprintf("https://%s/frame/web/v1/auth", duoHost)

	duoForm := url.Values{}
	duoForm.Add("parent", parent)
	duoForm.Add("java_version", "")
	duoForm.Add("java_version", "")
	duoForm.Add("flash_version", "")
	duoForm.Add("screen_resolution_width", "3008")
	duoForm.Add("screen_resolution_height", "1692")
	duoForm.Add("color_depth", "24")

	req, err := http.NewRequest("POST", duoSubmitURL, strings.NewReader(duoForm.Encode()))
	if err != nil {
		return "", errors.Wrap(err, "error building authentication request")
	}
	q := req.URL.Query()
	q.Add("tx", tx)
	req.URL.RawQuery = q.Encode()

	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

	res, err := oc.client.Do(req)
	if err != nil {
		return "", errors.Wrap(err, "error retrieving verify response")
	}

	// retrieve response from post
	doc, err := goquery.NewDocumentFromResponse(res)
	if err != nil {
		return "", errors.Wrap(err, "error parsing document")
	}

	// Duo cookie is returned here if mfa bypassed - immediatly return it if found
	duoTxCookie, ok := doc.Find("input[name=\"js_cookie\"]").Attr("value")
	if ok {
		if duoTxCookie == "" {
			return "", errors.Wrap(err, "duoMfaBypass: invalid response cookie")
		}
		return duoTxCookie, nil
	}

	// Duo cookie not found - continue with full MFA transaction
	duoSID, ok := doc.Find("input[name=\"sid\"]").Attr("value")
	if !ok {
		return "", errors.Wrap(err, "unable to locate saml response")
	}
	duoSID = html.UnescapeString(duoSID)

	//prompt for mfa type
	//supporting push, call, and passcode for now

	var token string

	var duoMfaOptions = []string{
		"Duo Push",
		"Phone Call",
		"Passcode",
	}

	duoMfaOption := prompter.Choose("Select a DUO MFA Option", duoMfaOptions)

	if duoMfaOptions[duoMfaOption] == "Passcode" {
		//get users DUO MFA Token
		token = prompter.StringRequired("Enter passcode")
	}

	// send mfa auth request
	duoSubmitURL = fmt.Sprintf("https://%s/frame/prompt", duoHost)

	duoForm = url.Values{}
	duoForm.Add("sid", duoSID)
	duoForm.Add("device", "phone1")
	duoForm.Add("factor", duoMfaOptions[duoMfaOption])
	duoForm.Add("out_of_date", "false")
	if duoMfaOptions[duoMfaOption] == "Passcode" {
		duoForm.Add("passcode", token)
	}

	req, err = http.NewRequest("POST", duoSubmitURL, strings.NewReader(duoForm.Encode()))
	if err != nil {
		return "", errors.Wrap(err, "error building authentication request")
	}

	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

	res, err = oc.client.Do(req)
	if err != nil {
		return "", errors.Wrap(err, "error retrieving verify response")
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", errors.Wrap(err, "error retrieving body from response")
	}

	resp := string(body)

	duoTxStat := gjson.Get(resp, "stat").String()
	duoTxID := gjson.Get(resp, "response.txid").String()
	if duoTxStat != "OK" {
		return "", errors.Wrap(err, "error authenticating mfa device")
	}

	// get duo cookie
	duoSubmitURL = fmt.Sprintf("https://%s/frame/status", duoHost)

	duoForm = url.Values{}
	duoForm.Add("sid", duoSID)
	duoForm.Add("txid", duoTxID)

	req, err = http.NewRequest("POST", duoSubmitURL, strings.NewReader(duoForm.Encode()))
	if err != nil {
		return "", errors.Wrap(err, "error building authentication request")
	}

	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

	res, err = oc.client.Do(req)
	if err != nil {
		return "", errors.Wrap(err, "error retrieving verify response")
	}

	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return "", errors.Wrap(err, "error retrieving body from response")
	}

	resp = string(body)

	duoTxResult := gjson.Get(resp, "response.result").String()
	duoResultURL := gjson.Get(resp, "response.result_url").String()

	log.Println(gjson.Get(resp, "response.status").String())

	if duoTxResult != "SUCCESS" {
		//poll as this is likely a push request
		for {
			time.Sleep(3 * time.Second)

			req, err = http.NewRequest("POST", duoSubmitURL, strings.NewReader(duoForm.Encode()))
			if err != nil {
				return "", errors.Wrap(err, "error building authentication request")
			}

			req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

			res, err = oc.client.Do(req)
			if err != nil {
				return "", errors.Wrap(err, "error retrieving verify response")
			}

			body, err = ioutil.ReadAll(res.Body)
			if err != nil {
				return "", errors.Wrap(err, "error retrieving body from response")
			}

			resp := string(body)

			duoTxResult = gjson.Get(resp, "response.result").String()
			duoResultURL = gjson.Get(resp, "response.result_url").String()

			log.Println(gjson.Get(resp, "response.status").String())

			if duoTxResult == "FAILURE" {
				return "", errors.Wrap(err, "failed to authenticate device")
			}

			if duoTxResult == "SUCCESS" {
				break
			}
		}
	}

	duoRequestURL := fmt.Sprintf("https://%s%s", duoHost, duoResultURL)
	req, err = http.NewRequest("POST", duoRequestURL, strings.NewReader(duoForm.Encode()))
	if err != nil {
		return "", errors.Wrap(err, "error constructing request object to result url")
	}

	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

	res, err = oc.client.Do(req)
	if err != nil {
		return "", errors.Wrap(err, "error retrieving duo result response")
	}

	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return "", errors.Wrap(err, "duoResultSubmit: error retrieving body from response")
	}

	resp = string(body)

	duoTxCookie = gjson.Get(resp, "response.cookie").String()
	if duoTxCookie == "" {
		return "", errors.Wrap(err, "duoResultSubmit: Unable to get response.cookie")
	}

	return duoTxCookie, nil
}