func()

in pkg/provider/jumpcloud/jumpcloud.go [70:222]


func (jc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) {
	var samlAssertion string
	var a AuthRequest
	re := regexp.MustCompile(jcSSOBaseURL)

	// Start by getting the XSRF Token
	res, err := jc.client.Get(xsrfURL)
	if err != nil {
		return samlAssertion, errors.Wrap(err, "error retieving XSRF Token")
	}

	// Grab the web response that has the xsrf in it
	xsrfBody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return samlAssertion, errors.Wrap(err, "error reading body of XSRF response")
	}

	// Unmarshall the answer and store the token
	var x = new(XSRF)
	err = json.Unmarshal(xsrfBody, &x)
	if err != nil {
		log.Fatalf("Error unmarshalling xsrf response! %v", err)
	}

	// Populate our Auth body for the POST
	a.Context = "sso"
	a.RedirectTo = re.ReplaceAllString(loginDetails.URL, "")
	a.Email = loginDetails.Username
	a.Password = loginDetails.Password

	authBody, err := json.Marshal(a)
	if err != nil {
		return samlAssertion, errors.Wrap(err, "failed to build auth request body")
	}

	// Generate our auth request
	req, err := http.NewRequest("POST", authSubmitURL, strings.NewReader(string(authBody)))
	if err != nil {
		return samlAssertion, errors.Wrap(err, "error building authentication request")
	}

	// Add the necessary headers to the auth request
	req.Header.Add("X-Xsrftoken", x.Token)
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Content-Type", "application/json")

	res, err = jc.client.Do(req)
	if err != nil {
		return samlAssertion, errors.Wrap(err, "error retrieving login form")
	}

	// Check if we get a 401.  If we did, and MFA is required, get the OTP and resubmit.
	// Otherwise log the authentication message as a fatal error.
	if res.StatusCode == 401 {

		// Grab the body from the response that has the message in it.
		messageBody, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return samlAssertion, errors.Wrap(err, "Error reading body")
		}

		// Unmarshall the body to get the message.
		var jcmsg = new(JCMessage)
		err = json.Unmarshal(messageBody, &jcmsg)
		if err != nil {
			log.Fatalf("Error unmarshalling message response! %v", err)
		}

		// If the error indicates something other than missing MFA, then it's fatal.
		if jcmsg.Message != "MFA required." {
			errMsg := fmt.Sprintf("Jumpcloud error: %s", jcmsg.Message)
			return samlAssertion, errors.Wrap(err, errMsg)
		}

		// Get the user's MFA token and re-build the body
		a.OTP = loginDetails.MFAToken
		if a.OTP == "" {
			a.OTP = prompter.StringRequired("MFA Token")
		}

		authBody, err = json.Marshal(a)
		if err != nil {
			return samlAssertion, errors.Wrap(err, "error building authentication req body after getting MFA Token")
		}

		// Re-request with our OTP
		req, err = http.NewRequest("POST", authSubmitURL, strings.NewReader(string(authBody)))
		if err != nil {
			return samlAssertion, errors.Wrap(err, "error building MFA authentication request")
		}

		// Re-add the necessary headers to our remade auth request
		req.Header.Add("X-Xsrftoken", x.Token)
		req.Header.Add("Accept", "application/json")
		req.Header.Add("Content-Type", "application/json")

		// Resubmit
		res, err = jc.client.Do(req)
		if err != nil {
			return samlAssertion, errors.Wrap(err, "error submitting MFA login form")
		}
	}

	// Check if our auth was successful
	if res.StatusCode == 200 {
		// Grab the body from the response that has the redirect in it.
		reDirBody, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return samlAssertion, errors.Wrap(err, "Error reading body")
		}

		// Unmarshall the body to get the redirect address
		var jcrd = new(JCRedirect)
		err = json.Unmarshal(reDirBody, &jcrd)
		if err != nil {
			log.Fatalf("Error unmarshalling redirectTo response! %v", err)
		}

		// Send the final GET for our SAML response
		res, err = jc.client.Get(jcrd.Address)
		if err != nil {
			return samlAssertion, errors.Wrap(err, "error submitting request for SAML value")
		}

		//try to extract SAMLResponse
		doc, err := goquery.NewDocumentFromReader(res.Body)
		if err != nil {
			return samlAssertion, errors.Wrap(err, "error parsing document")
		}

		doc.Find("input").Each(func(i int, s *goquery.Selection) {
			name, ok := s.Attr("name")
			if !ok {
				log.Fatalf("unable to locate IDP authentication form submit URL")
			}

			if name == "SAMLResponse" {
				val, ok := s.Attr("value")
				if !ok {
					log.Fatalf("unable to locate saml assertion value")
				}
				samlAssertion = val
			}

		})

	} else {
		errMsg := fmt.Sprintf("error when trying to auth, status code %d", res.StatusCode)
		return samlAssertion, errors.Wrap(err, errMsg)
	}

	return samlAssertion, nil
}