From cbe121a4fe2cfe0d9ee3df1477d8d3c903d548d4 Mon Sep 17 00:00:00 2001 From: Mark Wolfe Date: Thu, 19 Oct 2017 20:54:32 +1100 Subject: [PATCH] refactor(Input) Started moving user input out so it can be mocked --- Makefile | 8 ++++++- cmd/saml2aws/main.go | 2 +- pkg/prompter/prompter.go | 35 ++++++++++++++++++++++++++++ pkg/provider/okta/okta.go | 48 ++++++++++++++++++++++++++------------- pkg/provider/provider.go | 2 +- 5 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 pkg/prompter/prompter.go diff --git a/Makefile b/Makefile index 24c7c8c0e..9d77937f8 100644 --- a/Makefile +++ b/Makefile @@ -22,8 +22,10 @@ endif deps: glide # go get github.com/buildkite/github-release + go get -u github.com/alecthomas/gometalinter go get github.com/mitchellh/gox ./glide install + gometalinter --install compile: deps @rm -rf build/ @@ -36,6 +38,10 @@ compile: deps -output "build/{{.Dir}}_$(VERSION)_{{.OS}}_{{.Arch}}/$(NAME)" \ $(shell ./glide novendor) +# Run all the linters +lint: + gometalinter --vendor ./... + install: go install ./cmd/saml2aws @@ -58,4 +64,4 @@ clean: rm ./glide rm -fr ./build -.PHONY: default deps compile dist release test clean +.PHONY: default deps compile lint dist release test clean diff --git a/cmd/saml2aws/main.go b/cmd/saml2aws/main.go index 001054178..a6ebea23f 100644 --- a/cmd/saml2aws/main.go +++ b/cmd/saml2aws/main.go @@ -46,7 +46,7 @@ func buildCmdList(s kingpin.Settings) (target *[]string) { func configureLoginFlags(app *kingpin.Application) *commands.LoginFlags { c := &commands.LoginFlags{} - app.Flag("idp-account", "The name of the configured IDP account").Short('i').Default("default").StringVar(&c.IdpAccount) + app.Flag("idp-account", "The name of the configured IDP account").Short('a').Default("default").StringVar(&c.IdpAccount) app.Flag("profile", "The AWS profile to save the temporary credentials").Short('p').Default("saml").StringVar(&c.Profile) app.Flag("skip-verify", "Skip verification of server certificate.").Short('s').BoolVar(&c.SkipVerify) // app.Flag("timeout", "Override the default HTTP client timeout in seconds.").Short('t').IntVar(&c.Timeout) diff --git a/pkg/prompter/prompter.go b/pkg/prompter/prompter.go new file mode 100644 index 000000000..e51cace72 --- /dev/null +++ b/pkg/prompter/prompter.go @@ -0,0 +1,35 @@ +package prompter + +import prompt "github.com/segmentio/go-prompt" + +// Prompter handles prompting user for input +type Prompter interface { + RequestSecurityCode(pattern string) string + Choice(prompt string, options []string) string + StringRequired(pr string) string +} + +// CliPrompter used to prompt for cli input +type CliPrompter struct { +} + +// NewCli builds a new cli prompter +func NewCli() Prompter { + return &CliPrompter{} +} + +// RequestSecurityCode request a security code to be entered by the user +func (cli *CliPrompter) RequestSecurityCode(pattern string) string { + return prompt.StringRequired("\nSecurity Token [%s]\n", pattern) +} + +// Choice given the choice return the option selected +func (cli *CliPrompter) Choice(pr string, options []string) string { + selected := prompt.Choose(pr, options) + return options[selected] +} + +// StringRequired prompt for string which is required +func (cli *CliPrompter) StringRequired(pr string) string { + return prompt.StringRequired(pr) +} diff --git a/pkg/provider/okta/okta.go b/pkg/provider/okta/okta.go index 1e2de5662..a592dc2ec 100644 --- a/pkg/provider/okta/okta.go +++ b/pkg/provider/okta/okta.go @@ -11,9 +11,10 @@ import ( "strings" "time" + "github.com/versent/saml2aws/pkg/prompter" + "github.com/PuerkitoBio/goquery" "github.com/pkg/errors" - prompt "github.com/segmentio/go-prompt" "github.com/tidwall/gjson" "github.com/versent/saml2aws/pkg/cfg" "github.com/versent/saml2aws/pkg/creds" @@ -22,9 +23,15 @@ import ( "encoding/json" ) +var duoMFAOptions = []string{ + "Passcode", + "Duo Push", +} + // Client is a wrapper representing a Okta SAML client type Client struct { - client *provider.HTTPClient + client *provider.HTTPClient + prompter prompter.Prompter } // AuthRequest represents an mfa okta request @@ -50,7 +57,8 @@ func New(idpAccount *cfg.IDPAccount) (*Client, error) { } return &Client{ - client: client, + client: client, + prompter: prompter.NewCli(), }, nil } @@ -60,12 +68,19 @@ func (oc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) oktaEntryURL := fmt.Sprintf("https://%s", loginDetails.Hostname) oktaURL, err := url.Parse(oktaEntryURL) + if err != nil { + return samlAssertion, errors.Wrap(err, "error building oktaURL") + } + oktaOrgHost := oktaURL.Host //authenticate via okta api authReq := AuthRequest{Username: loginDetails.Username, Password: loginDetails.Password} authBody := new(bytes.Buffer) - json.NewEncoder(authBody).Encode(authReq) + err = json.NewEncoder(authBody).Encode(authReq) + if err != nil { + return samlAssertion, errors.Wrap(err, "error encoding authreq") + } authSubmitURL := fmt.Sprintf("https://%s/api/v1/authn", oktaOrgHost) @@ -102,7 +117,10 @@ func (oc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) // get duo host, signature & callback verifyReq := VerifyRequest{StateToken: stateToken} verifyBody := new(bytes.Buffer) - json.NewEncoder(verifyBody).Encode(verifyReq) + err := json.NewEncoder(verifyBody).Encode(verifyReq) + if err != nil { + return samlAssertion, errors.Wrap(err, "error encoding verifyReq") + } req, err := http.NewRequest("POST", oktaVerify, verifyBody) if err != nil { @@ -170,16 +188,11 @@ func (oc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) //only supporting push or passcode for now var token string - var mfaOptions = []string{ - "Passcode", - "Duo Push", - } + optionSelected := oc.prompter.Choice("Select a DUO MFA Option", duoMFAOptions) - mfaOption := prompt.Choose("Select a DUO MFA Option", mfaOptions) - - if mfaOptions[mfaOption] == "Passcode" { + if optionSelected == "Passcode" { //get users DUO MFA Token - token = prompt.StringRequired("Enter passcode") + token = oc.prompter.StringRequired("Enter passcode") } // send mfa auth request @@ -188,9 +201,9 @@ func (oc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) duoForm = url.Values{} duoForm.Add("sid", duoSID) duoForm.Add("device", "phone1") - duoForm.Add("factor", mfaOptions[mfaOption]) + duoForm.Add("factor", optionSelected) duoForm.Add("out_of_date", "false") - if mfaOptions[mfaOption] == "Passcode" { + if optionSelected == "Passcode" { duoForm.Add("passcode", token) } @@ -299,7 +312,10 @@ func (oc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) verifyReq = VerifyRequest{StateToken: stateToken} verifyBody = new(bytes.Buffer) - json.NewEncoder(verifyBody).Encode(verifyReq) + err = json.NewEncoder(verifyBody).Encode(verifyReq) + if err != nil { + return samlAssertion, errors.Wrap(err, "error encoding verifyReq") + } req, err = http.NewRequest("POST", oktaVerify, verifyBody) if err != nil { diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 47e62f73d..aabab28c7 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -13,7 +13,7 @@ type HTTPClient struct { } // NewHTTPClient configure the default http client used by the providers -func NewHTTPClient(tr *http.Transport) (*HTTPClient, error) { +func NewHTTPClient(tr http.RoundTripper) (*HTTPClient, error) { options := &cookiejar.Options{ PublicSuffixList: publicsuffix.List,