func verifyMFA()

in pkg/provider/onelogin/onelogin.go [204:366]


func verifyMFA(oc *Client, oauthToken, appID, resp string) (string, error) {
	stateToken := gjson.Get(resp, "state_token").String()
	// choose an mfa option if there are multiple enabled
	var option int
	var mfaOptions []string
	var preselected bool
	for n, id := range gjson.Get(resp, "devices.#.device_type").Array() {
		identifier := id.String()
		if val, ok := supportedMfaOptions[identifier]; ok {
			mfaOptions = append(mfaOptions, val)
			// If there is pre-selected MFA option (thorugh the --mfa flag), then set MFA option index and break early.
			if val == oc.MFA {
				option = n
				preselected = true
				break
			}
		} else {
			mfaOptions = append(mfaOptions, "UNSUPPORTED: "+identifier)
		}
	}
	if !preselected && len(mfaOptions) > 1 {
		option = prompter.Choose("Select which MFA option to use", mfaOptions)
	}

	factorID := gjson.Get(resp, fmt.Sprintf("devices.%d.device_id", option)).String()
	callbackURL := gjson.Get(resp, "callback_url").String()
	mfaIdentifer := gjson.Get(resp, fmt.Sprintf("devices.%d.device_type", option)).String()
	mfaDeviceID := gjson.Get(resp, fmt.Sprintf("devices.%d.device_id", option)).String()

	logger.WithField("factorID", factorID).WithField("callbackURL", callbackURL).WithField("mfaIdentifer", mfaIdentifer).Debug("MFA")

	if _, ok := supportedMfaOptions[mfaIdentifer]; !ok {
		return "", errors.New("unsupported mfa provider")
	}

	switch mfaIdentifer {
	// These MFA options doesn't need additional request (e.g. to send SMS or a push notification etc) since the user can generate the code using their MFA app of choice.
	case IdentifierTotpMfa, IdentifierYubiKey:
		break

	default:
		var verifyBody bytes.Buffer
		err := json.NewEncoder(&verifyBody).Encode(VerifyRequest{AppID: appID, DeviceID: mfaDeviceID, StateToken: stateToken})
		if err != nil {
			return "", errors.Wrap(err, "error encoding verifyReq")
		}

		req, err := http.NewRequest("POST", callbackURL, &verifyBody)
		if err != nil {
			return "", errors.Wrap(err, "error building verify request")
		}

		addContentHeaders(req)
		addAuthHeader(req, oauthToken)
		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)
		if gjson.Get(resp, "status.error").Bool() {
			msg := gjson.Get(resp, "status.message").String()
			return "", errors.New(msg)
		}
	}

	switch mfaIdentifer {
	case IdentifierSmsMfa, IdentifierTotpMfa, IdentifierYubiKey:
		verifyCode := prompter.StringRequired("Enter verification code")
		var verifyBody bytes.Buffer
		json.NewEncoder(&verifyBody).Encode(VerifyRequest{AppID: appID, DeviceID: mfaDeviceID, StateToken: stateToken, OTPToken: verifyCode})
		req, err := http.NewRequest("POST", callbackURL, &verifyBody)
		if err != nil {
			return "", errors.Wrap(err, "error building token post request")
		}

		addContentHeaders(req)
		addAuthHeader(req, oauthToken)
		res, err := oc.Client.Do(req)
		if err != nil {
			return "", errors.Wrap(err, "error retrieving token post response")
		}

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

		resp = string(body)

		message := gjson.Get(resp, "message").String()
		if res.StatusCode != 200 || message != MessageSuccess {
			return "", fmt.Errorf("HTTP %v: %s", res.StatusCode, message)
		}

		return gjson.Get(resp, "data").String(), nil

	case IdentifierOneLoginProtectMfa:
		// set the body payload to disable further push notifications (i.e. set do_not_notify to true)
		// https://developers.onelogin.com/api-docs/2/saml-assertions/verify-factor
		var verifyBody bytes.Buffer
		err := json.NewEncoder(&verifyBody).Encode(VerifyRequest{AppID: appID, DeviceID: mfaDeviceID, DoNotNotify: true, StateToken: stateToken})
		if err != nil {
			return "", errors.New("error encoding verify MFA request body")
		}
		req, err := http.NewRequest("POST", callbackURL, &verifyBody)
		if err != nil {
			return "", errors.Wrap(err, "error building token post request")
		}

		addContentHeaders(req)
		addAuthHeader(req, oauthToken)

		fmt.Printf("\nWaiting for approval, please check your OneLogin Protect app ...")
		started := time.Now()
		// loop until success, error, or timeout
		for {
			if time.Since(started) > time.Minute {
				log.Println(" Timeout")
				return "", errors.New("User did not accept MFA in time")
			}

			logger.Debug("Verifying with OneLogin Protect")
			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")
			}

			message := gjson.Get(string(body), "message").String()

			// on 'error' status
			if res.StatusCode != 200 {
				return "", fmt.Errorf("HTTP %v: %s", res.StatusCode, message)
			}

			switch true {
			case strings.Contains(message, "Authentication pending"):
				time.Sleep(time.Second)
				fmt.Print(".")

			case message == MessageSuccess:
				log.Println(" Approved")
				return gjson.Get(string(body), "data").String(), nil

			default:
				log.Println(" Error:")
				return "", errors.New("unsupported response from OneLogin, please raise ticket with saml2alibabacloud")
			}
		}
	}

	// catch all
	return "", errors.New("no mfa options provided")
}