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
}