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  }