pkg/provider/adfs2/rsa.go (163 lines of code) (raw):

package adfs2 import ( "bytes" "fmt" "io/ioutil" "net/http" "net/url" "strings" "github.com/PuerkitoBio/goquery" "github.com/aliyun/saml2alibabacloud/pkg/creds" "github.com/aliyun/saml2alibabacloud/pkg/dump" "github.com/aliyun/saml2alibabacloud/pkg/prompter" "github.com/pkg/errors" ) // Authenticate authenticate the user using the supplied login details func (ac *Client) authenticateRsa(loginDetails *creds.LoginDetails) (string, error) { authSubmitURL, authForm, err := ac.getLoginForm(loginDetails) if err != nil { return "", errors.Wrap(err, "error retrieving login form from idp") } doc, err := ac.postLoginForm(authSubmitURL, authForm) if err != nil { return "", errors.Wrap(err, "error posting login form to idp") } passcodeForm, passcodeActionURL, err := extractFormData(doc) if err != nil { return "", errors.Wrap(err, "error extractign login data") } /** * RSAv2 requires an additional POST to establish a context * https://github.com/torric1/AWSCLI-MFA-RSAv2 * https://gist.github.com/jgard/17262e0fc073c82bc7930db2f5603446 */ if passcodeForm.Get("AuthMethod") == "SecurIDv2Authentication" { doc, err = ac.postPasscodeForm(passcodeActionURL, passcodeForm) if err != nil { return "", errors.Wrap(err, "error posting passcode form") } } passcodeForm, passcodeActionURL, err = extractFormData(doc) if err != nil { return "", errors.Wrap(err, "error extracting mfa form data") } token := prompter.Password("Enter passcode") passcodeForm.Set("ChallengeQuestionAnswer", token) passcodeForm.Set("Passcode", token) passcodeForm.Del("submit") doc, err = ac.postPasscodeForm(passcodeActionURL, passcodeForm) if err != nil { return "", errors.Wrap(err, "error posting login form to idp") } rsaForm, rsaActionURL, err := extractFormData(doc) if err != nil { return "", errors.Wrap(err, "error extracting rsa form data") } if rsaForm.Get("SAMLResponse") == "" { nextCode := prompter.Password("Enter nextCode") rsaForm.Set("ChallengeQuestionAnswer", token) rsaForm.Set("NextCode", nextCode) rsaForm.Del("submit") doc, err = ac.postRSAForm(rsaActionURL, rsaForm) if err != nil { return "", errors.Wrap(err, "error posting rsa form") } } return extractSamlAssertion(doc) } func (ac *Client) postLoginForm(authSubmitURL string, authForm url.Values) (*goquery.Document, error) { req, err := http.NewRequest("POST", authSubmitURL, strings.NewReader(authForm.Encode())) if err != nil { return nil, errors.Wrap(err, "error building authentication request") } req.Header.Add("Content-Type", "application/x-www-form-urlencoded") logger.WithField("authSubmitURL", authSubmitURL).WithField("req", dump.RequestString(req)).Debug("POST") res, err := ac.client.Do(req) if err != nil { return nil, errors.Wrap(err, "error retrieving login form") } logger.WithField("status", res.StatusCode).WithField("authSubmitURL", authSubmitURL).WithField("res", dump.ResponseString(res)).Debug("POST") doc, err := goquery.NewDocumentFromReader(res.Body) if err != nil { return nil, errors.Wrap(err, "failed to build document from response") } return doc, nil } func (ac *Client) getLoginForm(loginDetails *creds.LoginDetails) (string, url.Values, error) { adfs2Url := fmt.Sprintf("%s/adfs/ls/IdpInitiatedSignOn.aspx?loginToRp=%s", loginDetails.URL, ac.idpAccount.AlibabaCloudURN) req, err := http.NewRequest("GET", adfs2Url, nil) if err != nil { return "", nil, err } res, err := ac.client.Do(req) if err != nil { return "", nil, errors.Wrap(err, "error retrieving login form") } logger.WithField("status", res.StatusCode).WithField("url", loginDetails.URL).WithField("res", dump.ResponseString(res)).Debug("GET") // Extract the form and actionURL from the previous response doc, err := goquery.NewDocumentFromReader(res.Body) if err != nil { return "", nil, errors.Wrap(err, "error extracting response data") } authForm, authSubmitURL, err := extractFormData(doc) if err != nil { return "", nil, errors.Wrap(err, "error extracting login data") } authForm.Set("UserName", loginDetails.Username) authForm.Set("Password", loginDetails.Password) return authSubmitURL, authForm, nil } func (ac *Client) postPasscodeForm(passcodeActionURL string, passcodeForm url.Values) (*goquery.Document, error) { req, err := http.NewRequest("POST", passcodeActionURL, strings.NewReader(passcodeForm.Encode())) if err != nil { return nil, errors.Wrap(err, "error building authentication request") } logger.WithField("actionURL", passcodeActionURL).WithField("req", dump.RequestString(req)).Debug("POST") res, err := ac.client.Do(req) if err != nil { return nil, errors.Wrap(err, "error retrieving login form") } logger.WithField("status", res.StatusCode).WithField("passcodeActionURL", passcodeActionURL).WithField("res", dump.ResponseString(res)).Debug("POST") doc, err := goquery.NewDocumentFromReader(res.Body) if err != nil { return nil, errors.Wrap(err, "failed to build document from response") } return doc, nil } func (ac *Client) postRSAForm(rsaSubmitURL string, form url.Values) (*goquery.Document, error) { req, err := http.NewRequest("POST", rsaSubmitURL, strings.NewReader(form.Encode())) if err != nil { return nil, errors.Wrap(err, "error building authentication request") } logger.WithField("rsaSubmitURL", rsaSubmitURL).WithField("req", dump.RequestString(req)).Debug("POST") res, err := ac.client.Do(req) if err != nil { return nil, errors.Wrap(err, "error retrieving login form") } logger.WithField("status", res.StatusCode).WithField("rsaSubmitURL", rsaSubmitURL).WithField("res", dump.ResponseString(res)).Debug("POST") data, err := ioutil.ReadAll(res.Body) if err != nil { return nil, errors.Wrap(err, "error retrieving body") } doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(data)) if err != nil { return nil, errors.Wrap(err, "error parsing document") } return doc, nil } func extractFormData(doc *goquery.Document) (url.Values, string, error) { formData := url.Values{} var actionURL string //get action url doc.Find("form").Each(func(i int, s *goquery.Selection) { action, ok := s.Attr("action") if !ok { return } actionURL = action }) // extract form data to passthrough doc.Find("input").Each(func(i int, s *goquery.Selection) { name, ok := s.Attr("name") if !ok { return } val, ok := s.Attr("value") if !ok || len(val) == 0 { return } formData.Set(name, val) }) return formData, actionURL, nil }