Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

catching other hid device errors and prompt for FIDO auth selections #668

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ bin/

# direnv
.envrc

saml2aws.iml
47 changes: 42 additions & 5 deletions pkg/provider/okta/okta.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ type mfaChallengeContext struct {
challengeResponseBody string
}

// mfaOption store the mfa position in response and mfa description
type mfaOption struct {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hinling-sonder WDYT about extracting the profile.authenticatorName from the json and add it to this struct to have a human-readable MFA option in the prompt? I found it really helpful to have this instead of the full profile field.

position int
mfaString string
}

// New creates a new Okta client
func New(idpAccount *cfg.IDPAccount) (*Client, error) {

Expand Down Expand Up @@ -272,10 +278,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) {
Expand Down Expand Up @@ -335,11 +342,23 @@ func findMfaOption(mfa string, mfaOptions []string, startAtIdx int) int {
return 0
}

func findAllMatchingMFA(mfa string, mfaOptions []string) []mfaOption {
var matchingMfas []mfaOption

for i, val := range mfaOptions {
if strings.Contains(strings.ToUpper(val), mfa) {
matchingMfas = append(matchingMfas, mfaOption{position: i, mfaString: 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()
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")

Expand Down Expand Up @@ -398,17 +417,27 @@ func verifyMfa(oc *Client, oktaOrgHost string, loginDetails *creds.LoginDetails,
// 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)
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" {
mfaOption = findMfaOption(oc.mfa, mfaOptions, 0)
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)
}
Expand Down Expand Up @@ -774,6 +803,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
Expand Down
11 changes: 10 additions & 1 deletion pkg/provider/okta/okta_webauthn.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package okta
import (
"errors"
"fmt"
"strings"
"time"

"github.com/marshallbrekka/go-u2fhost"
Expand Down Expand Up @@ -122,7 +123,15 @@ func (d *FidoClient) ChallengeU2F() (*SignedAssertion, error) {
prompted = true
}
default:
return responsePayload, err
errString := fmt.Sprintf("%s", err)
if strings.Contains(errString, "U2FHIDError") {
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 {
fmt.Printf("other errors? err: %s \n", err)
return responsePayload, err
}
}
}
}
Expand Down