Skip to content

Commit

Permalink
Merge pull request #1129 from eacherkan-aternity/fix/1128-azure-ad-en…
Browse files Browse the repository at this point in the history
…tropy-with-quiet

1128 - Print the entropy to stderr regardless of quiet mode
  • Loading branch information
mapkon authored Nov 20, 2023
2 parents bc68124 + af044ed commit f91a0bd
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 8 deletions.
26 changes: 24 additions & 2 deletions mocks/Prompter.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions pkg/prompter/pinentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ func (p *PinentryPrompter) Password(pr string) string {
return p.DefaultPrompter.Password(pr)
}

// Display is runniner the default Cli Display
func (p *PinentryPrompter) Display(pr string) {
p.DefaultPrompter.Display(pr)
}

// Run wraps a pinentry run. It sends the query to pinentry via stdin and
// reads its stdout to determine the user PIN.
// Pinentry uses an Assuan protocol
Expand Down
8 changes: 7 additions & 1 deletion pkg/prompter/pinentry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type FakeDefaultPrompter struct {
CalledString bool
CalledStringRequired bool
CalledPassword bool
CalledDisplay bool
}

// all the functions to implement the Prompter interface
Expand Down Expand Up @@ -63,7 +64,9 @@ func (f *FakeDefaultPrompter) Password(p string) string {
f.CalledPassword = true
return ""
}

func (f *FakeDefaultPrompter) Display(p string) {
f.CalledDisplay = true
}
func TestValidateAndSetPrompterShouldFailWithWrongInput(t *testing.T) {

// backing up the current prompters for the other tests
Expand Down Expand Up @@ -125,6 +128,9 @@ func TestChecksPinentryPrompterDefault(t *testing.T) {

_ = p.Password("random")
assert.True(t, fakeDefaultPrompter.CalledPassword)

p.Display("random")
assert.True(t, fakeDefaultPrompter.CalledDisplay)
}

func TestChecksPinentryPrompterCallsPinentryForRequestSecurityCode(t *testing.T) {
Expand Down
6 changes: 6 additions & 0 deletions pkg/prompter/prompter.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Prompter interface {
StringRequired(string) string
String(string, string) string
Password(string) string
Display(string)
}

// SetPrompter configure an aternate prompter to the default one
Expand Down Expand Up @@ -79,3 +80,8 @@ func String(pr string, defaultValue string) string {
func Password(pr string) string {
return ActivePrompter.Password(pr)
}

// Display prompt, no user input required
func Display(pr string) {
ActivePrompter.Display(pr)
}
4 changes: 4 additions & 0 deletions pkg/prompter/survey.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,7 @@ func (cli *CliPrompter) Password(pr string) string {
_ = survey.AskOne(prompt, &val, stdioOption())
return val
}

func (cli *CliPrompter) Display(pr string) {
_, _ = os.Stderr.WriteString(pr + "\n")
}
5 changes: 2 additions & 3 deletions pkg/provider/aad/aad.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"
Expand Down Expand Up @@ -463,9 +462,9 @@ func (ac *Client) processMfa(mfas []userProof, convergedResponse *ConvergedRespo
}
if mfaReq.AuthMethodID == "PhoneAppNotification" && i == 0 {
if mfaResp.Entropy == 0 {
log.Println("Phone approval required.")
prompter.Display("Phone approval required.")
} else {
log.Printf("Phone approval required. Entropy is: %d", mfaResp.Entropy)
prompter.Display(fmt.Sprintf("Phone approval required. Entropy is: %d", mfaResp.Entropy))
}
}

Expand Down
80 changes: 80 additions & 0 deletions pkg/provider/aad/aad_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math"
mrand "math/rand"
"net/http"
Expand All @@ -18,6 +19,8 @@ import (
"testing"
"text/template"

"github.com/stretchr/testify/mock"

"github.com/google/uuid"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -62,6 +65,8 @@ type FixtureData struct {
UserName string // [email protected]
UserNameUrlEncoded string // exampleuser%40exampledomain.com
SErrorCode string // 50058
AuthMethodId string // OneWaySMS
Entropy string // 0
}

var fixtureData *FixtureData
Expand Down Expand Up @@ -89,6 +94,8 @@ func genFixtureData() *FixtureData {
ProofUpToken: genBase64Fixture(400),
UserName: "[email protected]",
UserNameUrlEncoded: "exampleuser%40exampledomain.com",
AuthMethodId: "OneWaySMS",
Entropy: "0",
}
})
return fixtureData
Expand Down Expand Up @@ -360,6 +367,72 @@ func Test_Authenticate(t *testing.T) {
require.Error(t, err)
require.Contains(t, err.Error(), "502031")
})
t.Run("Default login with KMSI and MFA with entropy", func(t *testing.T) {
entropy := genIntFixture(2)
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/index", "/applications/redirecttofederatedapplication.aspx":
writeFixtureBytes(t, w, r, "ConvergedSignIn.html", FixtureData{
UrlPost: "/defaultLogin",
UrlGetCredentialType: "/getCredentialType",
})
case "/getCredentialType":
writeFixtureBytes(t, w, r, "GetCredentialType_default.json", FixtureData{})
case "/defaultLogin":
writeFixtureBytes(t, w, r, "KmsiInterrupt.html", FixtureData{
UrlPost: "/hForm",
})
case "/hForm":
writeFixtureBytes(t, w, r, "HiddenForm.html", FixtureData{
UrlHiddenForm: "/sRequest",
})
case "/sRequest":
writeFixtureBytes(t, w, r, "SAMLRequest.html", FixtureData{
UrlSamlRequest: "/sResponse?SAMLRequest=ExampleValue",
})
case "/sResponse":
writeFixtureBytes(t, w, r, "ConvergedTFA.html", FixtureData{
UrlPost: "/processAuth",
UrlBeginAuth: "/beginAuth",
UrlEndAuth: "/endAuth",
})
case "/beginAuth":
writeFixtureBytes(t, w, r, "BeginAuth.json", FixtureData{
AuthMethodId: "PhoneAppNotification",
Entropy: entropy,
})
case "/endAuth":
writeFixtureBytes(t, w, r, "EndAuth.json", FixtureData{
AuthMethodId: "PhoneAppNotification",
Entropy: entropy,
})
case "/processAuth":
writeFixtureBytes(t, w, r, "SAMLResponse.html", FixtureData{})
default:
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
}
}))
defer ts.Close()

pr := prompter.NewCli()
prompter.SetPrompter(pr)

ac, loginDetails := setupTestClient(t, ts)

orig := os.Stderr
r, w, _ := os.Pipe()
os.Stderr = w

got, err := ac.Authenticate(loginDetails)

os.Stderr = orig
w.Close()
out, _ := io.ReadAll(r)

require.Contains(t, string(out), "Phone approval required. Entropy is: "+entropy)
require.Nil(t, err)
require.NotEmpty(t, got)
})
t.Run("ADFS login with KMSI and MFA", func(t *testing.T) {
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
Expand Down Expand Up @@ -413,6 +486,7 @@ func Test_Authenticate(t *testing.T) {
pr := &mocks.Prompter{}
prompter.SetPrompter(pr)
pr.Mock.On("StringRequired", "Enter verification code").Return("000000")
pr.Mock.On("Display", mock.Anything).Return()

ac, loginDetails := setupTestClient(t, ts)
got, err := ac.Authenticate(loginDetails)
Expand Down Expand Up @@ -492,6 +566,12 @@ func responseFixtures(host string, variableFixture FixtureData) *FixtureData {
} else {
fixtureData.UrlSamlRequest = ""
}
if variableFixture.AuthMethodId != "" {
fixtureData.AuthMethodId = variableFixture.AuthMethodId
}
if variableFixture.Entropy != "" {
fixtureData.Entropy = variableFixture.Entropy
}
return fixtureData
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/provider/aad/testdata/BeginAuth.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"Success":true,"ResultValue":"Success","Message":null,"AuthMethodId":"OneWaySMS","ErrCode":0,"Retry":false,"FlowToken":"{{.SFT}}","Ctx":"{{.Ctx}}","SessionId":"{{.SessionId}}","CorrelationId":"{{.ClientRequestId}}","Timestamp":"2020-01-01T00:00:00Z","Entropy":0}
{"Success":true,"ResultValue":"Success","Message":null,"AuthMethodId":"{{.AuthMethodId}}","ErrCode":0,"Retry":false,"FlowToken":"{{.SFT}}","Ctx":"{{.Ctx}}","SessionId":"{{.SessionId}}","CorrelationId":"{{.ClientRequestId}}","Timestamp":"2020-01-01T00:00:00Z","Entropy":{{.Entropy}}}
2 changes: 1 addition & 1 deletion pkg/provider/aad/testdata/EndAuth.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"Success":true,"ResultValue":"Success","Message":null,"AuthMethodId":"OneWaySMS","ErrCode":0,"Retry":false,"FlowToken":"{{.SFT}}","Ctx":"{{.Ctx}}","SessionId":"{{.SessionId}}","CorrelationId":"{{.ClientRequestId}}","Timestamp":"2020-01-01T00:00:00Z","Entropy":0}
{"Success":true,"ResultValue":"Success","Message":null,"AuthMethodId":"{{.AuthMethodId}}","ErrCode":0,"Retry":false,"FlowToken":"{{.SFT}}","Ctx":"{{.Ctx}}","SessionId":"{{.SessionId}}","CorrelationId":"{{.ClientRequestId}}","Timestamp":"2020-01-01T00:00:00Z","Entropy":{{.Entropy}}}

0 comments on commit f91a0bd

Please sign in to comment.