github.com/versent/saml2aws@v2.17.0+incompatible/pkg/provider/adfs2/rsa.go (about) 1 package adfs2 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "net/url" 9 "strings" 10 11 "github.com/PuerkitoBio/goquery" 12 "github.com/pkg/errors" 13 "github.com/versent/saml2aws/pkg/creds" 14 "github.com/versent/saml2aws/pkg/dump" 15 "github.com/versent/saml2aws/pkg/prompter" 16 ) 17 18 // Authenticate authenticate the user using the supplied login details 19 func (ac *Client) authenticateRsa(loginDetails *creds.LoginDetails) (string, error) { 20 21 authSubmitURL, authForm, err := ac.getLoginForm(loginDetails) 22 if err != nil { 23 return "", errors.Wrap(err, "error retrieving login form from idp") 24 } 25 26 doc, err := ac.postLoginForm(authSubmitURL, authForm) 27 if err != nil { 28 return "", errors.Wrap(err, "error posting login form to idp") 29 } 30 31 passcodeForm, passcodeActionURL, err := extractFormData(doc) 32 if err != nil { 33 return "", errors.Wrap(err, "error extracting mfa form data") 34 } 35 36 token := prompter.Password("Enter passcode") 37 38 passcodeForm.Set("Passcode", token) 39 passcodeForm.Del("submit") 40 41 doc, err = ac.postPasscodeForm(passcodeActionURL, passcodeForm) 42 if err != nil { 43 return "", errors.Wrap(err, "error posting login form to idp") 44 } 45 46 rsaForm, rsaActionURL, err := extractFormData(doc) 47 if err != nil { 48 return "", errors.Wrap(err, "error extracting rsa form data") 49 } 50 51 if rsaForm.Get("SAMLResponse") == "" { 52 nextCode := prompter.Password("Enter nextCode") 53 54 rsaForm.Set("NextCode", nextCode) 55 rsaForm.Del("submit") 56 57 doc, err = ac.postRSAForm(rsaActionURL, rsaForm) 58 if err != nil { 59 return "", errors.Wrap(err, "error posting rsa form") 60 } 61 } 62 return extractSamlAssertion(doc) 63 } 64 65 func (ac *Client) postLoginForm(authSubmitURL string, authForm url.Values) (*goquery.Document, error) { 66 67 req, err := http.NewRequest("POST", authSubmitURL, strings.NewReader(authForm.Encode())) 68 if err != nil { 69 return nil, errors.Wrap(err, "error building authentication request") 70 } 71 72 req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 73 74 logger.WithField("authSubmitURL", authSubmitURL).WithField("req", dump.RequestString(req)).Debug("POST") 75 76 res, err := ac.client.Do(req) 77 if err != nil { 78 return nil, errors.Wrap(err, "error retrieving login form") 79 } 80 81 logger.WithField("status", res.StatusCode).WithField("authSubmitURL", authSubmitURL).WithField("res", dump.ResponseString(res)).Debug("POST") 82 83 doc, err := goquery.NewDocumentFromResponse(res) 84 if err != nil { 85 return nil, errors.Wrap(err, "failed to build document from response") 86 } 87 88 return doc, nil 89 } 90 91 func (ac *Client) getLoginForm(loginDetails *creds.LoginDetails) (string, url.Values, error) { 92 93 adfs2Url := fmt.Sprintf("%s/adfs/ls/IdpInitiatedSignOn.aspx?loginToRp=%s", loginDetails.URL, ac.idpAccount.AmazonWebservicesURN) 94 95 req, err := http.NewRequest("GET", adfs2Url, nil) 96 if err != nil { 97 return "", nil, err 98 } 99 100 res, err := ac.client.Do(req) 101 if err != nil { 102 return "", nil, errors.Wrap(err, "error retrieving login form") 103 } 104 105 logger.WithField("status", res.StatusCode).WithField("url", loginDetails.URL).WithField("res", dump.ResponseString(res)).Debug("GET") 106 107 // REALLY need to extract the form and actionURL from the previous response 108 109 authForm := url.Values{} 110 authForm.Add("UserName", loginDetails.Username) 111 authForm.Add("Password", loginDetails.Password) 112 authForm.Add("AuthMethod", "FormsAuthentication") 113 114 authSubmitURL := fmt.Sprintf("%s/adfs/ls/idpinitiatedsignon", loginDetails.URL) 115 116 return authSubmitURL, authForm, nil 117 } 118 119 func (ac *Client) postPasscodeForm(passcodeActionURL string, passcodeForm url.Values) (*goquery.Document, error) { 120 121 req, err := http.NewRequest("POST", passcodeActionURL, strings.NewReader(passcodeForm.Encode())) 122 if err != nil { 123 return nil, errors.Wrap(err, "error building authentication request") 124 } 125 126 logger.WithField("actionURL", passcodeActionURL).WithField("req", dump.RequestString(req)).Debug("POST") 127 128 res, err := ac.client.Do(req) 129 if err != nil { 130 return nil, errors.Wrap(err, "error retrieving login form") 131 } 132 133 logger.WithField("status", res.StatusCode).WithField("passcodeActionURL", passcodeActionURL).WithField("res", dump.ResponseString(res)).Debug("POST") 134 135 doc, err := goquery.NewDocumentFromResponse(res) 136 if err != nil { 137 return nil, errors.Wrap(err, "failed to build document from response") 138 } 139 140 return doc, nil 141 } 142 143 func (ac *Client) postRSAForm(rsaSubmitURL string, form url.Values) (*goquery.Document, error) { 144 145 req, err := http.NewRequest("POST", rsaSubmitURL, strings.NewReader(form.Encode())) 146 if err != nil { 147 return nil, errors.Wrap(err, "error building authentication request") 148 } 149 150 logger.WithField("rsaSubmitURL", rsaSubmitURL).WithField("req", dump.RequestString(req)).Debug("POST") 151 152 res, err := ac.client.Do(req) 153 if err != nil { 154 return nil, errors.Wrap(err, "error retrieving login form") 155 } 156 157 logger.WithField("status", res.StatusCode).WithField("rsaSubmitURL", rsaSubmitURL).WithField("res", dump.ResponseString(res)).Debug("POST") 158 159 data, err := ioutil.ReadAll(res.Body) 160 if err != nil { 161 return nil, errors.Wrap(err, "error retrieving body") 162 } 163 164 doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(data)) 165 if err != nil { 166 return nil, errors.Wrap(err, "error parsing document") 167 } 168 169 return doc, nil 170 } 171 172 func extractFormData(doc *goquery.Document) (url.Values, string, error) { 173 formData := url.Values{} 174 var actionURL string 175 176 //get action url 177 doc.Find("form").Each(func(i int, s *goquery.Selection) { 178 action, ok := s.Attr("action") 179 if !ok { 180 return 181 } 182 actionURL = action 183 }) 184 185 // extract form data to passthrough 186 doc.Find("input").Each(func(i int, s *goquery.Selection) { 187 name, ok := s.Attr("name") 188 if !ok { 189 return 190 } 191 val, ok := s.Attr("value") 192 if !ok { 193 return 194 } 195 formData.Add(name, val) 196 }) 197 198 return formData, actionURL, nil 199 }