From ea80b68080330bdfea7bf10ddc350854fbe07b59 Mon Sep 17 00:00:00 2001 From: Hinling Yeung Date: Sun, 16 May 2021 22:51:35 -0700 Subject: [PATCH 1/9] catch extra hid usb device errors --- pkg/provider/okta/okta_webauthn.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/provider/okta/okta_webauthn.go b/pkg/provider/okta/okta_webauthn.go index e1c7de14e..664e924b0 100644 --- a/pkg/provider/okta/okta_webauthn.go +++ b/pkg/provider/okta/okta_webauthn.go @@ -3,6 +3,7 @@ package okta import ( "errors" "fmt" + "strings" "time" "github.com/marshallbrekka/go-u2fhost" @@ -122,7 +123,14 @@ func (d *FidoClient) ChallengeU2F() (*SignedAssertion, error) { prompted = true } default: - return responsePayload, err + errString := fmt.Sprintf("%s", err) + if strings.Contains(errString, "U2FHIDError") { + fmt.Println("err: %s. Let's keep looping till times out", err) + } else if strings.Contains(errString, "hidapi: hid_error is not implemented yet") { + fmt.Println("err: %s. Let's keep looping till times out", err) + } else { + return responsePayload, err + } } } } From 326d73c13ab1c281d6137c692ceb91a46caeffd2 Mon Sep 17 00:00:00 2001 From: Hinling Yeung Date: Mon, 17 May 2021 09:36:39 -0700 Subject: [PATCH 2/9] format err message better print out devices info deference the device pointer and print print out MFA also add more logging and catch more errors? fix logging still fixing logging more logging not an address add more logging add more logging around mfaoptions always prompt for which MFA getting all the matching mfas back add more logging --- pkg/provider/okta/okta.go | 29 ++++++++++++++++++++++++++++- pkg/provider/okta/okta_webauthn.go | 8 ++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/pkg/provider/okta/okta.go b/pkg/provider/okta/okta.go index 7361e31d4..3702d8d83 100644 --- a/pkg/provider/okta/okta.go +++ b/pkg/provider/okta/okta.go @@ -324,17 +324,32 @@ func extractSAMLResponse(doc *goquery.Document) (v string, ok bool) { } func findMfaOption(mfa string, mfaOptions []string, startAtIdx int) int { + fmt.Printf("========> mfaOptions: %s \n", mfaOptions) + fmt.Printf("========> mfa: %s \n", mfa) + fmt.Printf("========> startAtIdx: %s \n", startAtIdx) for idx, val := range mfaOptions { if startAtIdx > idx { continue } if strings.HasPrefix(strings.ToUpper(val), mfa) { + fmt.Printf("========> return idx: %i \n", idx) return idx } } return 0 } +func findAllMatchingMFA(mfa string, mfaOptions []string) []string { + var matchingMfas []string + + for _, val := range mfaOptions { + if strings.HasPrefix(strings.ToUpper(val), mfa) { + matchingMfas = append(matchingMfas, val) + } + } + return matchingMfas +} + func getMfaChallengeContext(oc *Client, mfaOption int, resp string) (*mfaChallengeContext, error) { stateToken := gjson.Get(resp, "stateToken").String() factorID := gjson.Get(resp, fmt.Sprintf("_embedded.factors.%d.id", mfaOption)).String() @@ -393,6 +408,7 @@ func getMfaChallengeContext(oc *Client, mfaOption int, resp string) (*mfaChallen } func verifyMfa(oc *Client, oktaOrgHost string, loginDetails *creds.LoginDetails, resp string) (string, error) { + fmt.Printf("=========> resp with MFAs: %s \n", resp) stateToken := gjson.Get(resp, "stateToken").String() // choose an mfa option if there are multiple enabled @@ -400,6 +416,7 @@ func verifyMfa(oc *Client, oktaOrgHost string, loginDetails *creds.LoginDetails, var mfaOptions []string for i := range gjson.Get(resp, "_embedded.factors").Array() { identifier := parseMfaIdentifer(resp, i) + fmt.Println("=====> MFA: %s ", identifier) if val, ok := supportedMfaOptions[identifier]; ok { mfaOptions = append(mfaOptions, val) } else { @@ -407,8 +424,14 @@ func verifyMfa(oc *Client, oktaOrgHost string, loginDetails *creds.LoginDetails, } } + if strings.ToUpper(oc.mfa) != "AUTO" { - mfaOption = findMfaOption(oc.mfa, mfaOptions, 0) + matchingMfaOptions := findAllMatchingMFA(oc.mfa, mfaOptions) + if len(matchingMfaOptions) > 1 { + mfaOption = prompter.Choose("Select which MFA option to use", matchingMfaOptions) + } else { + mfaOption = findMfaOption(oc.mfa, mfaOptions, 0) + } } else if len(mfaOptions) > 1 { mfaOption = prompter.Choose("Select which MFA option to use", mfaOptions) } @@ -798,6 +821,7 @@ func fidoWebAuthn(oc *Client, oktaOrgHost string, challengeContext *mfaChallenge } signedAssertion, err = fidoClient.ChallengeU2F() + fmt.Printf("=======> signedAssertion: %s \n", signedAssertion) if err != nil { // if this error is not a bad key error we are done if _, ok := err.(*u2fhost.BadKeyHandleError); !ok { @@ -805,7 +829,10 @@ func fidoWebAuthn(oc *Client, oktaOrgHost string, challengeContext *mfaChallenge } // check if there is another fido device and try that + fmt.Printf("=======> lastMfaOption: %s \n", lastMfaOption) nextMfaOption := findMfaOption(oc.mfa, mfaOptions, lastMfaOption) + fmt.Printf("=======> nextMfaOption: %s \n", nextMfaOption) + fmt.Printf("=======> mfa options: %s \n", mfaOptions) if nextMfaOption <= lastMfaOption { return "", errors.Wrap(err, "tried all MFA options") } diff --git a/pkg/provider/okta/okta_webauthn.go b/pkg/provider/okta/okta_webauthn.go index 664e924b0..b0f60f632 100644 --- a/pkg/provider/okta/okta_webauthn.go +++ b/pkg/provider/okta/okta_webauthn.go @@ -125,10 +125,13 @@ func (d *FidoClient) ChallengeU2F() (*SignedAssertion, error) { default: errString := fmt.Sprintf("%s", err) if strings.Contains(errString, "U2FHIDError") { - fmt.Println("err: %s. Let's keep looping till times out", err) + fmt.Printf("Let's keep looping till times out. err: %s \n", err) } else if strings.Contains(errString, "hidapi: hid_error is not implemented yet") { - fmt.Println("err: %s. Let's keep looping till times out", err) + fmt.Printf("Let's keep looping till times out. err: %s \n", err) + /*} else if strings.Contains(errString, "The provided key handle is not present on the device"){ + fmt.Printf("Let's keep looping till times out. err: %s \n", err)*/ } else { + fmt.Printf("other errors? err: %s \n", err) return responsePayload, err } } @@ -150,6 +153,7 @@ func (*U2FDeviceFinder) findDevice() (u2fhost.Device, error) { for i, device := range allDevices { err = device.Open() + fmt.Printf("========> Devices: %s \n", *device) if err != nil { device.Close() From 6cae29120ea997f87224d13c3407c8feaec0137a Mon Sep 17 00:00:00 2001 From: Hinling Yeung Date: Wed, 19 May 2021 01:03:23 -0700 Subject: [PATCH 3/9] clean up and add profile info --- pkg/provider/okta/okta.go | 29 ++++++++++------------------- pkg/provider/okta/okta_webauthn.go | 1 - 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/pkg/provider/okta/okta.go b/pkg/provider/okta/okta.go index 3702d8d83..76631043d 100644 --- a/pkg/provider/okta/okta.go +++ b/pkg/provider/okta/okta.go @@ -272,10 +272,11 @@ func getStateTokenFromOktaPageBody(responseBody string) (string, error) { return strings.Replace(match[1], `\x2D`, "-", -1), nil } -func parseMfaIdentifer(json string, arrayPosition int) string { +func parseMfaIdentifer(json string, arrayPosition int) (string, string) { mfaProvider := gjson.Get(json, fmt.Sprintf("_embedded.factors.%d.provider", arrayPosition)).String() factorType := strings.ToUpper(gjson.Get(json, fmt.Sprintf("_embedded.factors.%d.factorType", arrayPosition)).String()) - return fmt.Sprintf("%s %s", mfaProvider, factorType) + profile := gjson.Get(json, fmt.Sprintf("_embedded.factors.%d.profile", arrayPosition)).String() + return fmt.Sprintf("%s %s", mfaProvider, factorType), fmt.Sprintf("%s %s -- %s", mfaProvider, factorType, profile) } func (oc *Client) handleFormRedirect(ctx context.Context, doc *goquery.Document) (context.Context, *http.Request, error) { @@ -324,15 +325,11 @@ func extractSAMLResponse(doc *goquery.Document) (v string, ok bool) { } func findMfaOption(mfa string, mfaOptions []string, startAtIdx int) int { - fmt.Printf("========> mfaOptions: %s \n", mfaOptions) - fmt.Printf("========> mfa: %s \n", mfa) - fmt.Printf("========> startAtIdx: %s \n", startAtIdx) for idx, val := range mfaOptions { if startAtIdx > idx { continue } if strings.HasPrefix(strings.ToUpper(val), mfa) { - fmt.Printf("========> return idx: %i \n", idx) return idx } } @@ -354,7 +351,7 @@ func getMfaChallengeContext(oc *Client, mfaOption int, resp string) (*mfaChallen stateToken := gjson.Get(resp, "stateToken").String() factorID := gjson.Get(resp, fmt.Sprintf("_embedded.factors.%d.id", mfaOption)).String() oktaVerify := gjson.Get(resp, fmt.Sprintf("_embedded.factors.%d._links.verify.href", mfaOption)).String() - mfaIdentifer := parseMfaIdentifer(resp, mfaOption) + mfaIdentifer, _ := parseMfaIdentifer(resp, mfaOption) logger.WithField("factorID", factorID).WithField("oktaVerify", oktaVerify).WithField("mfaIdentifer", mfaIdentifer).Debug("MFA") @@ -408,29 +405,27 @@ func getMfaChallengeContext(oc *Client, mfaOption int, resp string) (*mfaChallen } func verifyMfa(oc *Client, oktaOrgHost string, loginDetails *creds.LoginDetails, resp string) (string, error) { - fmt.Printf("=========> resp with MFAs: %s \n", resp) stateToken := gjson.Get(resp, "stateToken").String() // choose an mfa option if there are multiple enabled mfaOption := 0 var mfaOptions []string + var profiles []string for i := range gjson.Get(resp, "_embedded.factors").Array() { - identifier := parseMfaIdentifer(resp, i) - fmt.Println("=====> MFA: %s ", identifier) + identifier,profile := parseMfaIdentifer(resp, i) if val, ok := supportedMfaOptions[identifier]; ok { mfaOptions = append(mfaOptions, val) } else { mfaOptions = append(mfaOptions, "UNSUPPORTED: "+identifier) } + profiles = append(profiles, profile) } if strings.ToUpper(oc.mfa) != "AUTO" { - matchingMfaOptions := findAllMatchingMFA(oc.mfa, mfaOptions) - if len(matchingMfaOptions) > 1 { - mfaOption = prompter.Choose("Select which MFA option to use", matchingMfaOptions) - } else { - mfaOption = findMfaOption(oc.mfa, mfaOptions, 0) + allMatchingMfaOptinos := findAllMatchingMFA(oc.mfa, profiles) + if len(allMatchingMfaOptinos) > 1 { + mfaOption = prompter.Choose("Select which MFA option to use", allMatchingMfaOptinos) } } else if len(mfaOptions) > 1 { mfaOption = prompter.Choose("Select which MFA option to use", mfaOptions) @@ -821,7 +816,6 @@ func fidoWebAuthn(oc *Client, oktaOrgHost string, challengeContext *mfaChallenge } signedAssertion, err = fidoClient.ChallengeU2F() - fmt.Printf("=======> signedAssertion: %s \n", signedAssertion) if err != nil { // if this error is not a bad key error we are done if _, ok := err.(*u2fhost.BadKeyHandleError); !ok { @@ -829,10 +823,7 @@ func fidoWebAuthn(oc *Client, oktaOrgHost string, challengeContext *mfaChallenge } // check if there is another fido device and try that - fmt.Printf("=======> lastMfaOption: %s \n", lastMfaOption) nextMfaOption := findMfaOption(oc.mfa, mfaOptions, lastMfaOption) - fmt.Printf("=======> nextMfaOption: %s \n", nextMfaOption) - fmt.Printf("=======> mfa options: %s \n", mfaOptions) if nextMfaOption <= lastMfaOption { return "", errors.Wrap(err, "tried all MFA options") } diff --git a/pkg/provider/okta/okta_webauthn.go b/pkg/provider/okta/okta_webauthn.go index b0f60f632..13a08ce6e 100644 --- a/pkg/provider/okta/okta_webauthn.go +++ b/pkg/provider/okta/okta_webauthn.go @@ -153,7 +153,6 @@ func (*U2FDeviceFinder) findDevice() (u2fhost.Device, error) { for i, device := range allDevices { err = device.Open() - fmt.Printf("========> Devices: %s \n", *device) if err != nil { device.Close() From 0ab92995e1103cb761852ff77ba40fa23d0a152b Mon Sep 17 00:00:00 2001 From: Hinling Yeung Date: Wed, 19 May 2021 01:27:22 -0700 Subject: [PATCH 4/9] clean up catching hid errors --- pkg/provider/okta/okta_webauthn.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/provider/okta/okta_webauthn.go b/pkg/provider/okta/okta_webauthn.go index 13a08ce6e..3f2eb48d8 100644 --- a/pkg/provider/okta/okta_webauthn.go +++ b/pkg/provider/okta/okta_webauthn.go @@ -128,8 +128,6 @@ func (d *FidoClient) ChallengeU2F() (*SignedAssertion, error) { fmt.Printf("Let's keep looping till times out. err: %s \n", err) } else if strings.Contains(errString, "hidapi: hid_error is not implemented yet") { fmt.Printf("Let's keep looping till times out. err: %s \n", err) - /*} else if strings.Contains(errString, "The provided key handle is not present on the device"){ - fmt.Printf("Let's keep looping till times out. err: %s \n", err)*/ } else { fmt.Printf("other errors? err: %s \n", err) return responsePayload, err From 42fb055638b12709ef24913171e530fe28325545 Mon Sep 17 00:00:00 2001 From: Hinling Yeung Date: Wed, 19 May 2021 01:37:56 -0700 Subject: [PATCH 5/9] fix lint --- pkg/provider/okta/okta.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/provider/okta/okta.go b/pkg/provider/okta/okta.go index 76631043d..47ea588a6 100644 --- a/pkg/provider/okta/okta.go +++ b/pkg/provider/okta/okta.go @@ -412,7 +412,7 @@ func verifyMfa(oc *Client, oktaOrgHost string, loginDetails *creds.LoginDetails, var mfaOptions []string var profiles []string for i := range gjson.Get(resp, "_embedded.factors").Array() { - identifier,profile := parseMfaIdentifer(resp, i) + identifier, profile := parseMfaIdentifer(resp, i) if val, ok := supportedMfaOptions[identifier]; ok { mfaOptions = append(mfaOptions, val) } else { @@ -421,7 +421,6 @@ func verifyMfa(oc *Client, oktaOrgHost string, loginDetails *creds.LoginDetails, profiles = append(profiles, profile) } - if strings.ToUpper(oc.mfa) != "AUTO" { allMatchingMfaOptinos := findAllMatchingMFA(oc.mfa, profiles) if len(allMatchingMfaOptinos) > 1 { From f3aa4e6db18b9b4b9e1589ebde11a64b1a036c6b Mon Sep 17 00:00:00 2001 From: Hinling Yeung Date: Wed, 9 Feb 2022 11:37:03 -0600 Subject: [PATCH 6/9] fix mfa options --- .gitignore | 2 ++ pkg/provider/okta/okta.go | 37 ++++++++++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 186524f9f..b7bbf8893 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ bin/ # direnv .envrc + +saml2aws.iml diff --git a/pkg/provider/okta/okta.go b/pkg/provider/okta/okta.go index 47ea588a6..34d51cc15 100644 --- a/pkg/provider/okta/okta.go +++ b/pkg/provider/okta/okta.go @@ -85,6 +85,11 @@ type mfaChallengeContext struct { challengeResponseBody string } +type mfaOption struct { + position int + mfaString string +} + // New creates a new Okta client func New(idpAccount *cfg.IDPAccount) (*Client, error) { @@ -276,7 +281,7 @@ func parseMfaIdentifer(json string, arrayPosition int) (string, string) { mfaProvider := gjson.Get(json, fmt.Sprintf("_embedded.factors.%d.provider", arrayPosition)).String() factorType := strings.ToUpper(gjson.Get(json, fmt.Sprintf("_embedded.factors.%d.factorType", arrayPosition)).String()) profile := gjson.Get(json, fmt.Sprintf("_embedded.factors.%d.profile", arrayPosition)).String() - return fmt.Sprintf("%s %s", mfaProvider, factorType), fmt.Sprintf("%s %s -- %s", mfaProvider, factorType, profile) + return fmt.Sprintf( "%s %s", mfaProvider, factorType), fmt.Sprintf("%s %s -- %s", mfaProvider, factorType, profile) } func (oc *Client) handleFormRedirect(ctx context.Context, doc *goquery.Document) (context.Context, *http.Request, error) { @@ -336,13 +341,14 @@ func findMfaOption(mfa string, mfaOptions []string, startAtIdx int) int { return 0 } -func findAllMatchingMFA(mfa string, mfaOptions []string) []string { - var matchingMfas []string +func findAllMatchingMFA(mfa string, mfaOptions []string) []mfaOption { + var matchingMfas []mfaOption - for _, val := range mfaOptions { - if strings.HasPrefix(strings.ToUpper(val), mfa) { - matchingMfas = append(matchingMfas, val) + for i, val := range mfaOptions { + if strings.Contains(strings.ToUpper(val), mfa) { + matchingMfas = append(matchingMfas, mfaOption{position: i, mfaString: val}) } + } return matchingMfas } @@ -422,9 +428,14 @@ func verifyMfa(oc *Client, oktaOrgHost string, loginDetails *creds.LoginDetails, } if strings.ToUpper(oc.mfa) != "AUTO" { - allMatchingMfaOptinos := findAllMatchingMFA(oc.mfa, profiles) - if len(allMatchingMfaOptinos) > 1 { - mfaOption = prompter.Choose("Select which MFA option to use", allMatchingMfaOptinos) + allMatchingMfaOptions := findAllMatchingMFA(oc.mfa, profiles) + if len(allMatchingMfaOptions) > 1 { + pickedOption := prompter.Choose("Select which MFA option to use", getMfaStringArr(allMatchingMfaOptions)) + mfaOption = allMatchingMfaOptions[pickedOption].position + } else if len(allMatchingMfaOptions) == 1 { + mfaOption = allMatchingMfaOptions[0].position + } else { + return "", errors.New("No Matching MFA registered on OKTA. Here are your registered MFAs: " + fmt.Sprintf("%v\n", profiles)) } } else if len(mfaOptions) > 1 { mfaOption = prompter.Choose("Select which MFA option to use", mfaOptions) @@ -791,6 +802,14 @@ func verifyMfa(oc *Client, oktaOrgHost string, loginDetails *creds.LoginDetails, return "", errors.New("no mfa options provided") } +func getMfaStringArr(options []mfaOption) []string { + var list []string + for _, option := range options { + list = append(list, option.mfaString) + } + return list +} + func fidoWebAuthn(oc *Client, oktaOrgHost string, challengeContext *mfaChallengeContext, mfaOption int, stateToken string, mfaOptions []string, resp string) (string, error) { var signedAssertion *SignedAssertion From e665e913d3b32c958a63005523113f7cdadca0cb Mon Sep 17 00:00:00 2001 From: Hinling Yeung Date: Thu, 10 Feb 2022 10:37:04 -0600 Subject: [PATCH 7/9] lint fix --- pkg/provider/okta/okta.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/provider/okta/okta.go b/pkg/provider/okta/okta.go index 34d51cc15..7e062eca4 100644 --- a/pkg/provider/okta/okta.go +++ b/pkg/provider/okta/okta.go @@ -85,8 +85,9 @@ type mfaChallengeContext struct { challengeResponseBody string } +// mfaOption store the mfa position in response and mfa description type mfaOption struct { - position int + position int mfaString string } @@ -281,7 +282,7 @@ func parseMfaIdentifer(json string, arrayPosition int) (string, string) { mfaProvider := gjson.Get(json, fmt.Sprintf("_embedded.factors.%d.provider", arrayPosition)).String() factorType := strings.ToUpper(gjson.Get(json, fmt.Sprintf("_embedded.factors.%d.factorType", arrayPosition)).String()) profile := gjson.Get(json, fmt.Sprintf("_embedded.factors.%d.profile", arrayPosition)).String() - return fmt.Sprintf( "%s %s", mfaProvider, factorType), fmt.Sprintf("%s %s -- %s", mfaProvider, factorType, profile) + return fmt.Sprintf("%s %s", mfaProvider, factorType), fmt.Sprintf("%s %s -- %s", mfaProvider, factorType, profile) } func (oc *Client) handleFormRedirect(ctx context.Context, doc *goquery.Document) (context.Context, *http.Request, error) { From 5faac571a8eb7beffed6ed933d04c8da0795c1bb Mon Sep 17 00:00:00 2001 From: Florent Vilmart <364568+flovilmart@users.noreply.github.com> Date: Tue, 22 Feb 2022 17:31:02 -0500 Subject: [PATCH 8/9] Adds support for fetching artifactory api keys (#4) --- pkg/page/form.go | 1 + pkg/provider/okta/okta.go | 127 +++++++++++++++++++++++++++++++++++--- 2 files changed, 120 insertions(+), 8 deletions(-) diff --git a/pkg/page/form.go b/pkg/page/form.go index cdbb6e0be..65884a7d3 100644 --- a/pkg/page/form.go +++ b/pkg/page/form.go @@ -46,6 +46,7 @@ func NewFormFromDocument(doc *goquery.Document, formFilter string) (*Form, error if formFilter == "" { formFilter = "form[action]" } + formSelection := doc.Find(formFilter).First() if formSelection.Size() != 1 { return nil, fmt.Errorf("could not find form") diff --git a/pkg/provider/okta/okta.go b/pkg/provider/okta/okta.go index 7e062eca4..0966fd8e1 100644 --- a/pkg/provider/okta/okta.go +++ b/pkg/provider/okta/okta.go @@ -121,9 +121,7 @@ func New(idpAccount *cfg.IDPAccount) (*Client, error) { type ctxKey string -// Authenticate logs into Okta and returns a SAML response -func (oc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) { - +func (oc *Client) InitSession(loginDetails *creds.LoginDetails) (string, error) { oktaURL, err := url.Parse(loginDetails.URL) if err != nil { return "", errors.Wrap(err, "error building oktaURL") @@ -185,10 +183,31 @@ func (oc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) } } + return oktaSessionToken, err +} + +// Authenticate logs into Okta and returns a SAML response +// Legacy method that should not be used anymore +func (oc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) { + oktaSessionToken, err := oc.InitSession(loginDetails) + if err != nil { + return "", errors.Wrap(err, "error authenticating with okta") + } + + return oc.AuthenticateWithService(oktaSessionToken, loginDetails) +} + +func (oc *Client) AuthenticateWithService(oktaSessionToken string, loginDetails *creds.LoginDetails) (string, error) { + oktaURL, err := url.Parse(loginDetails.URL) + if err != nil { + return "", errors.Wrap(err, "error building oktaURL") + } + + oktaOrgHost := oktaURL.Host //now call saml endpoint oktaSessionRedirectURL := fmt.Sprintf("https://%s/login/sessionCookieRedirect", oktaOrgHost) - req, err = http.NewRequest("GET", oktaSessionRedirectURL, nil) + req, err := http.NewRequest("GET", oktaSessionRedirectURL, nil) if err != nil { return "", errors.Wrap(err, "error building authentication request") } @@ -215,7 +234,16 @@ func (oc *Client) follow(ctx context.Context, req *http.Request, loginDetails *c var handler func(context.Context, *goquery.Document) (context.Context, *http.Request, error) - if docIsFormRedirectToTarget(doc, oc.targetURL) { + if pageIsJfrog(res) { + logger.WithField("type", "saml-response-to-jfrog").Debug("doc detect") + + var apiKey, err = oc.fetchArtifactoryApiKey() + if err != nil { + return "", err + } + return apiKey, nil + + } else if docIsFormRedirectToTarget(doc, oc.targetURL) { logger.WithField("type", "saml-response-to-aws").Debug("doc detect") if samlResponse, ok := extractSAMLResponse(doc); ok { decodedSamlResponse, err := base64.StdEncoding.DecodeString(samlResponse) @@ -228,12 +256,12 @@ func (oc *Client) follow(ctx context.Context, req *http.Request, loginDetails *c } else if docIsFormSamlRequest(doc) { logger.WithField("type", "saml-request").Debug("doc detect") handler = oc.handleFormRedirect - } else if docIsFormResume(doc) { - logger.WithField("type", "resume").Debug("doc detect") - handler = oc.handleFormRedirect } else if docIsFormSamlResponse(doc) { logger.WithField("type", "saml-response").Debug("doc detect") handler = oc.handleFormRedirect + } else if docIsFormResume(doc) { + logger.WithField("type", "resume").Debug("doc detect") + handler = oc.handleFormRedirect } else { req, err = http.NewRequest("GET", loginDetails.URL, nil) if err != nil { @@ -248,6 +276,7 @@ func (oc *Client) follow(ctx context.Context, req *http.Request, loginDetails *c return "", errors.Wrap(err, "error retrieving body from response") } stateToken, err := getStateTokenFromOktaPageBody(string(body)) + if err != nil { return "", errors.Wrap(err, "error retrieving saml response") } @@ -294,6 +323,10 @@ func (oc *Client) handleFormRedirect(ctx context.Context, doc *goquery.Document) return ctx, req, err } +func pageIsJfrog(res *http.Response) bool { + return res.Request.URL.String() == "https://sonder.jfrog.io/ui/login/" +} + func docIsFormSamlRequest(doc *goquery.Document) bool { return doc.Find("input[name=\"SAMLRequest\"]").Size() == 1 } @@ -330,6 +363,84 @@ func extractSAMLResponse(doc *goquery.Document) (v string, ok bool) { return doc.Find("input[name=\"SAMLResponse\"]").Attr("value") } +func (oc *Client) fetchArtifactoryApiKey() (string, error) { + // Fetch the current user email, needed for the subsequent requests + req, err := http.NewRequest("GET", "https://sonder.jfrog.io/ui/api/v1/ui/auth/current", nil) + req.Header.Set("X-Requested-With", "XMLHttpRequest") + + if err != nil { + return "", err + } + + res, err := oc.client.Do(req) + if err != nil { + return "", err + } + var body struct { + Name string `json:"name"` + } + err = json.NewDecoder(res.Body).Decode(&body) + if err != nil { + return "", err + } + + logger.WithField("user", body.Name).Debug("Authenticated as") + + // Force initializing the user profile to get a USER_PROFILE_TOKEN set in the cookies + var jsonData = []byte(`{}`) + req, _ = http.NewRequest("POST", "https://sonder.jfrog.io/ui/api/v1/ui/userProfile", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Requested-With", "XMLHttpRequest") + + _, err = oc.client.Do(req) + if err != nil { + logger.WithField("error", err).Debug("there was an error") + return "", err + } + + userApiKeyURL := "https://sonder.jfrog.io/ui/api/v1/ui/userApiKey/" + body.Name + // Attempt to get the api key from the call. + req, _ = http.NewRequest("GET", userApiKeyURL, nil) + + apiKey, _ := oc.getApiKey(req) + if apiKey != "" { + return apiKey, nil + } + // Fallback and create the API key if not existing + req, _ = http.NewRequest("POST", userApiKeyURL, nil) + req.Header.Set("X-Requested-With", "XMLHttpRequest") + basicAuth := base64.StdEncoding.EncodeToString([]byte(body.Name + ":")) + req.Header.Set("X-JFrog-Reauthentication", "Basic "+basicAuth) + + apiKey, err = oc.getApiKey(req) + + if apiKey != "" { + return apiKey, nil + } + return "", err +} + +func (oc *Client) getApiKey(req *http.Request) (string, error) { + var apiKeyBody struct { + ApiKey string `json:"apiKey,omitempty"` + } + res, err := oc.client.Do(req) + + if err != nil { + return "", err + } + + err = json.NewDecoder(res.Body).Decode(&apiKeyBody) + if err != nil { + return "", err + } + + if apiKeyBody.ApiKey != "" { + return apiKeyBody.ApiKey, nil + } + return "", err +} + func findMfaOption(mfa string, mfaOptions []string, startAtIdx int) int { for idx, val := range mfaOptions { if startAtIdx > idx { From 22d0659677fbf2522ea4534b93f262a02b8911da Mon Sep 17 00:00:00 2001 From: Florent Vilmart <364568+flovilmart@users.noreply.github.com> Date: Tue, 8 Mar 2022 14:53:50 -0500 Subject: [PATCH 9/9] Update artifactory auth to return username as well (#5) --- pkg/provider/okta/okta.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/provider/okta/okta.go b/pkg/provider/okta/okta.go index 0966fd8e1..ab19406d8 100644 --- a/pkg/provider/okta/okta.go +++ b/pkg/provider/okta/okta.go @@ -237,11 +237,11 @@ func (oc *Client) follow(ctx context.Context, req *http.Request, loginDetails *c if pageIsJfrog(res) { logger.WithField("type", "saml-response-to-jfrog").Debug("doc detect") - var apiKey, err = oc.fetchArtifactoryApiKey() + var apiKey, userName, err = oc.fetchArtifactoryAuth() if err != nil { return "", err } - return apiKey, nil + return userName + ":" + apiKey, nil } else if docIsFormRedirectToTarget(doc, oc.targetURL) { logger.WithField("type", "saml-response-to-aws").Debug("doc detect") @@ -363,25 +363,25 @@ func extractSAMLResponse(doc *goquery.Document) (v string, ok bool) { return doc.Find("input[name=\"SAMLResponse\"]").Attr("value") } -func (oc *Client) fetchArtifactoryApiKey() (string, error) { +func (oc *Client) fetchArtifactoryAuth() (string, string, error) { // Fetch the current user email, needed for the subsequent requests req, err := http.NewRequest("GET", "https://sonder.jfrog.io/ui/api/v1/ui/auth/current", nil) req.Header.Set("X-Requested-With", "XMLHttpRequest") if err != nil { - return "", err + return "", "", err } res, err := oc.client.Do(req) if err != nil { - return "", err + return "", "", err } var body struct { Name string `json:"name"` } err = json.NewDecoder(res.Body).Decode(&body) if err != nil { - return "", err + return "", "", err } logger.WithField("user", body.Name).Debug("Authenticated as") @@ -395,7 +395,7 @@ func (oc *Client) fetchArtifactoryApiKey() (string, error) { _, err = oc.client.Do(req) if err != nil { logger.WithField("error", err).Debug("there was an error") - return "", err + return "", "", err } userApiKeyURL := "https://sonder.jfrog.io/ui/api/v1/ui/userApiKey/" + body.Name @@ -404,7 +404,7 @@ func (oc *Client) fetchArtifactoryApiKey() (string, error) { apiKey, _ := oc.getApiKey(req) if apiKey != "" { - return apiKey, nil + return apiKey, body.Name, nil } // Fallback and create the API key if not existing req, _ = http.NewRequest("POST", userApiKeyURL, nil) @@ -415,9 +415,9 @@ func (oc *Client) fetchArtifactoryApiKey() (string, error) { apiKey, err = oc.getApiKey(req) if apiKey != "" { - return apiKey, nil + return apiKey, body.Name, nil } - return "", err + return "", "", err } func (oc *Client) getApiKey(req *http.Request) (string, error) {