mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-02 23:31:08 +08:00
264 lines
7.4 KiB
Go
264 lines
7.4 KiB
Go
// Copyright 2022 Gravitational, Inc
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package webauthncli_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/gravitational/teleport/lib/auth/touchid"
|
|
wancli "github.com/gravitational/teleport/lib/auth/webauthncli"
|
|
"github.com/gravitational/teleport/lib/utils/prompt"
|
|
)
|
|
|
|
func TestDefaultPrompt_PromptCredential(t *testing.T) {
|
|
oldStdin := prompt.Stdin()
|
|
t.Cleanup(func() { prompt.SetStdin(oldStdin) })
|
|
|
|
llamaCred := &wancli.CredentialInfo{
|
|
User: wancli.UserInfo{
|
|
Name: "llama",
|
|
},
|
|
}
|
|
alpacaCred := &wancli.CredentialInfo{
|
|
User: wancli.UserInfo{
|
|
Name: "alpaca",
|
|
},
|
|
}
|
|
camelCred := &wancli.CredentialInfo{
|
|
User: wancli.UserInfo{
|
|
Name: "camel",
|
|
},
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
tests := []struct {
|
|
name string
|
|
fakeReader *prompt.FakeReader
|
|
ctx context.Context
|
|
creds []*wancli.CredentialInfo
|
|
wantCred *wancli.CredentialInfo
|
|
wantErr string
|
|
// Optional, verifies output text.
|
|
wantOut string
|
|
}{
|
|
{
|
|
name: "credential by number (1)",
|
|
fakeReader: prompt.NewFakeReader().AddString("1"), // sorted by name
|
|
creds: []*wancli.CredentialInfo{llamaCred, alpacaCred, camelCred},
|
|
wantCred: alpacaCred,
|
|
},
|
|
{
|
|
name: "credential by number (2)",
|
|
fakeReader: prompt.NewFakeReader().AddString("3"), // sorted by name
|
|
creds: []*wancli.CredentialInfo{llamaCred, alpacaCred, camelCred},
|
|
wantCred: llamaCred,
|
|
},
|
|
{
|
|
name: "credential by name",
|
|
fakeReader: prompt.NewFakeReader().AddString("alpaca"),
|
|
creds: []*wancli.CredentialInfo{llamaCred, alpacaCred, camelCred},
|
|
wantCred: alpacaCred,
|
|
},
|
|
{
|
|
name: "loops until correct",
|
|
fakeReader: prompt.NewFakeReader().
|
|
AddString("bad").
|
|
AddString("5").
|
|
AddString("llama"),
|
|
creds: []*wancli.CredentialInfo{llamaCred, alpacaCred, camelCred},
|
|
wantCred: llamaCred,
|
|
},
|
|
{
|
|
name: "NOK empty credentials errors",
|
|
fakeReader: prompt.NewFakeReader(),
|
|
creds: []*wancli.CredentialInfo{},
|
|
wantErr: "empty credentials",
|
|
},
|
|
{
|
|
name: "output text",
|
|
fakeReader: prompt.NewFakeReader().AddString("llama"),
|
|
creds: []*wancli.CredentialInfo{llamaCred, alpacaCred, camelCred},
|
|
wantCred: llamaCred,
|
|
wantOut: `[1] alpaca
|
|
[2] camel
|
|
[3] llama
|
|
` + wancli.NewDefaultPrompt(ctx, nil).PromptCredentialMessage,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
prompt.SetStdin(test.fakeReader)
|
|
|
|
out := &strings.Builder{}
|
|
p := wancli.NewDefaultPrompt(ctx, out)
|
|
got, err := p.PromptCredential(test.creds)
|
|
switch {
|
|
case err == nil && test.wantErr == "": // OK
|
|
case err == nil && test.wantErr != "":
|
|
fallthrough
|
|
case !strings.Contains(err.Error(), test.wantErr):
|
|
t.Fatalf("PromptCredential returned err = %v, want %q", err, test.wantErr)
|
|
}
|
|
assert.Equal(t, test.wantCred, got, "PromptCredential mismatch")
|
|
if test.wantOut != "" {
|
|
// Contains so we don't trip on punctuation from prompt.Input.
|
|
assert.Contains(t, out.String(), test.wantOut, "output mismatch")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type funcToPicker func([]*wancli.CredentialInfo) (*wancli.CredentialInfo, error)
|
|
|
|
func (f funcToPicker) PromptCredential(creds []*wancli.CredentialInfo) (*wancli.CredentialInfo, error) {
|
|
return f(creds)
|
|
}
|
|
|
|
func TestToTouchIDCredentialPicker(t *testing.T) {
|
|
indexPicker := func(i int) func([]*wancli.CredentialInfo) (*wancli.CredentialInfo, error) {
|
|
return func(creds []*wancli.CredentialInfo) (*wancli.CredentialInfo, error) {
|
|
return creds[i], nil
|
|
}
|
|
}
|
|
errorPicker := func(err error) func([]*wancli.CredentialInfo) (*wancli.CredentialInfo, error) {
|
|
return func(_ []*wancli.CredentialInfo) (*wancli.CredentialInfo, error) {
|
|
return nil, err
|
|
}
|
|
}
|
|
bogusPicker := func(resp *wancli.CredentialInfo) func([]*wancli.CredentialInfo) (*wancli.CredentialInfo, error) {
|
|
return func(_ []*wancli.CredentialInfo) (*wancli.CredentialInfo, error) {
|
|
return resp, nil
|
|
}
|
|
}
|
|
|
|
creds := []*touchid.CredentialInfo{
|
|
{
|
|
CredentialID: "id1",
|
|
User: touchid.UserInfo{
|
|
UserHandle: []byte("llama"),
|
|
Name: "llama",
|
|
},
|
|
},
|
|
{
|
|
CredentialID: "id2",
|
|
User: touchid.UserInfo{
|
|
UserHandle: []byte("alpaca"),
|
|
Name: "alpaca",
|
|
},
|
|
},
|
|
{
|
|
CredentialID: "id3",
|
|
User: touchid.UserInfo{
|
|
UserHandle: []byte("camel"),
|
|
Name: "camel",
|
|
},
|
|
},
|
|
{
|
|
CredentialID: "id4",
|
|
User: touchid.UserInfo{
|
|
UserHandle: []byte("llama"),
|
|
Name: "llama",
|
|
},
|
|
},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
picker func([]*wancli.CredentialInfo) (*wancli.CredentialInfo, error)
|
|
creds []*touchid.CredentialInfo
|
|
wantCred *touchid.CredentialInfo
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "picks first credential",
|
|
picker: indexPicker(0),
|
|
creds: creds,
|
|
wantCred: creds[0],
|
|
},
|
|
{
|
|
name: "picks middle credential",
|
|
picker: indexPicker(1),
|
|
creds: creds,
|
|
wantCred: creds[1],
|
|
},
|
|
{
|
|
name: "picks last credential",
|
|
picker: indexPicker(3),
|
|
creds: creds,
|
|
wantCred: creds[3],
|
|
},
|
|
{
|
|
name: "picker errors",
|
|
picker: errorPicker(errors.New("something real bad happened")),
|
|
creds: creds,
|
|
wantErr: "something real bad happened",
|
|
},
|
|
{
|
|
name: "picker returns bogus credential",
|
|
picker: bogusPicker(&wancli.CredentialInfo{
|
|
// It doesn't matter that the fields match, the pointer is not present
|
|
// in the original array.
|
|
ID: []byte(creds[0].CredentialID),
|
|
User: wancli.UserInfo{
|
|
UserHandle: creds[0].User.UserHandle,
|
|
Name: creds[0].User.Name,
|
|
},
|
|
}),
|
|
creds: creds,
|
|
wantErr: "returned invalid credential",
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
picker := wancli.ToTouchIDCredentialPicker(funcToPicker(test.picker))
|
|
got, err := picker.PromptCredential(test.creds)
|
|
if test.wantErr == "" {
|
|
assert.NoError(t, err, "PromptCredential error mismatch")
|
|
} else {
|
|
assert.ErrorContains(t, err, test.wantErr, "PromptCredential error mismatch")
|
|
}
|
|
assert.Equal(t, test.wantCred, got, "PromptCredential cred mismatch")
|
|
})
|
|
}
|
|
|
|
t.Run("credentials converted correctly", func(t *testing.T) {
|
|
picker := wancli.ToTouchIDCredentialPicker(funcToPicker(
|
|
func(ci []*wancli.CredentialInfo) (*wancli.CredentialInfo, error) {
|
|
require.Len(t, ci, len(creds), "creds length mismatch")
|
|
for i, c := range ci {
|
|
other := creds[i]
|
|
|
|
// We are bordering a change detection test here, so let's just check
|
|
// the ID and make sure the fields we care about aren't empty.
|
|
assert.Equal(t, []byte(other.CredentialID), c.ID, "creds[%v].CredentialID mismatch", i)
|
|
assert.NotEmpty(t, c.User.UserHandle, "creds[%v].User.UserHandle empty", i)
|
|
assert.NotEmpty(t, c.User.Name, "creds[%v].User.Name empty", i)
|
|
}
|
|
return ci[0], nil
|
|
}))
|
|
|
|
_, err := picker.PromptCredential(creds)
|
|
require.NoError(t, err, "PromptCredential errored unexpectedly")
|
|
})
|
|
}
|