Skip to content

Commit

Permalink
Merge pull request #1222 from wlonkly/master
Browse files Browse the repository at this point in the history
Okta + Duo: Allow passcodes from mobile app
  • Loading branch information
mapkon authored Mar 7, 2024
2 parents 3b3c4a0 + 22e706d commit 0c98c07
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 59 deletions.
3 changes: 2 additions & 1 deletion pkg/provider/okta/okta.go
Original file line number Diff line number Diff line change
Expand Up @@ -975,7 +975,8 @@ func verifyMfa(oc *Client, oktaOrgHost string, loginDetails *creds.LoginDetails,
duoMfaOptions = append(duoMfaOptions, "Duo Push")
}

if doc.Find("option[value=\"token\"]").Length() > 0 {
if doc.Find("option[value=\"token\"]").Length() > 0 ||
doc.Find("option[value=\"phone1\"]").Length() > 0 {
duoMfaOptions = append(duoMfaOptions, "Passcode")
}

Expand Down
179 changes: 121 additions & 58 deletions pkg/provider/okta/okta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ import (

"github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert"
"github.com/versent/saml2aws/v2/mocks"
"github.com/versent/saml2aws/v2/pkg/cfg"
"github.com/versent/saml2aws/v2/pkg/creds"
"github.com/versent/saml2aws/v2/pkg/prompter"
"github.com/versent/saml2aws/v2/pkg/provider"
)

Expand Down Expand Up @@ -266,8 +268,107 @@ func TestVerifyMfa(t *testing.T) {
}

func TestVerifyMfa_Duo(t *testing.T) {
t.Run("Duo Push", func(t *testing.T) {
ts := setupTestDuoHttpServer(t, "Duo Push")
defer ts.Close()
oc, loginDetails := setupTestClient(t, ts, "DUO")

err := oc.setDeviceTokenCookie(loginDetails)
assert.Nil(t, err)

var out bytes.Buffer
log.SetOutput(&out)
context, err := verifyMfa(oc, "host-from-argument", &creds.LoginDetails{DuoMFAOption: "Duo Push"}, fmt.Sprintf(`{
"stateToken": "TOKEN_1",
"status": "MFA_REQUIRED",
"_embedded": {
"factors": [
{
"id": "factor_id",
"provider": "DUO",
"factorType": "web",
"_links": {
"verify": {
"href": "%s/verify",
"hints": {
"allow": [
"POST"
]
}
}
}
}
]
}
}`, ts.URL))
log.SetOutput(os.Stderr)
assert.Nil(t, err)
assert.Equal(t, "session-token-fffffff", context)
})

t.Run("Passcode", func(t *testing.T) {
ts := setupTestDuoHttpServer(t, "Passcode")
defer ts.Close()
oc, loginDetails := setupTestClient(t, ts, "DUO")

err := oc.setDeviceTokenCookie(loginDetails)
assert.Nil(t, err)

pr := &mocks.Prompter{}
prompter.SetPrompter(pr)
pr.Mock.On("StringRequired", "Enter passcode").Return("000000")

var out bytes.Buffer
log.SetOutput(&out)
context, err := verifyMfa(oc, "host-from-argument", &creds.LoginDetails{DuoMFAOption: "Passcode"}, fmt.Sprintf(`{
"stateToken": "TOKEN_1",
"status": "MFA_REQUIRED",
"_embedded": {
"factors": [
{
"id": "factor_id",
"provider": "DUO",
"factorType": "web",
"_links": {
"verify": {
"href": "%s/verify",
"hints": {
"allow": [
"POST"
]
}
}
}
}
]
}
}`, ts.URL))
log.SetOutput(os.Stderr)
assert.Nil(t, err)
assert.Equal(t, "session-token-fffffff", context)
})
}

func setupTestClient(t *testing.T, ts *httptest.Server, mfa string) (*Client, *creds.LoginDetails) {
testTransport := http.DefaultTransport.(*http.Transport).Clone()
testTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
opts := &provider.HTTPClientOptions{IsWithRetries: false}
client, _ := provider.NewHTTPClient(testTransport, opts)
ac := &Client{
client: client,
targetURL: ts.URL,
mfa: mfa,
disableSessions: false,
rememberDevice: true,
}
loginDetails := &creds.LoginDetails{URL: ts.URL, Username: "[email protected]", Password: "test123"}
return ac, loginDetails
}

func setupTestDuoHttpServer(t *testing.T, duoFactor string) *httptest.Server {
verifyCounter := 0
statusCounter := 0

ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/frame/web/v1/auth":
Expand Down Expand Up @@ -323,12 +424,25 @@ func TestVerifyMfa_Duo(t *testing.T) {
assert.Nil(t, err)

query, err = url.ParseQuery(string(body))
assert.Equal(t, url.Values{
"device": {"phone1"},
"factor": {"Duo Push"},
"out_of_date": {"false"},
"sid": {"secret_sid"},
}, query)

switch duoFactor {
case "Duo Push":
assert.Equal(t, url.Values{
"device": {"phone1"},
"factor": {"Duo Push"},
"out_of_date": {"false"},
"sid": {"secret_sid"},
}, query)
case "Passcode":
assert.Equal(t, url.Values{
"device": {"phone1"},
"factor": {"Passcode"},
"passcode": {"000000"},
"out_of_date": {"false"},
"sid": {"secret_sid"},
}, query)
}

assert.Nil(t, err)

_, err = w.Write([]byte(`{"stat": "OK", "response": {"txid": "txid_1234"}}`))
Expand Down Expand Up @@ -482,59 +596,8 @@ func TestVerifyMfa_Duo(t *testing.T) {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
}
}))
defer ts.Close()

t.Run("Duo", func(t *testing.T) {
oc, loginDetails := setupTestClient(t, ts, "DUO")

err := oc.setDeviceTokenCookie(loginDetails)
assert.Nil(t, err)

var out bytes.Buffer
log.SetOutput(&out)
context, err := verifyMfa(oc, "host-from-argument", &creds.LoginDetails{DuoMFAOption: "Duo Push"}, fmt.Sprintf(`{
"stateToken": "TOKEN_1",
"status": "MFA_REQUIRED",
"_embedded": {
"factors": [
{
"id": "factor_id",
"provider": "DUO",
"factorType": "web",
"_links": {
"verify": {
"href": "%s/verify",
"hints": {
"allow": [
"POST"
]
}
}
}
}
]
}
}`, ts.URL))
log.SetOutput(os.Stderr)
assert.Nil(t, err)
assert.Equal(t, "session-token-fffffff", context)
})
}

func setupTestClient(t *testing.T, ts *httptest.Server, mfa string) (*Client, *creds.LoginDetails) {
testTransport := http.DefaultTransport.(*http.Transport).Clone()
testTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
opts := &provider.HTTPClientOptions{IsWithRetries: false}
client, _ := provider.NewHTTPClient(testTransport, opts)
ac := &Client{
client: client,
targetURL: ts.URL,
mfa: mfa,
disableSessions: false,
rememberDevice: true,
}
loginDetails := &creds.LoginDetails{URL: ts.URL, Username: "[email protected]", Password: "test123"}
return ac, loginDetails
return ts
}

func TestSetDeviceTokenCookie(t *testing.T) {
Expand Down

0 comments on commit 0c98c07

Please sign in to comment.