Skip to content

Commit

Permalink
Move newly-added Webauthn tests out of gocheck (#8074)
Browse files Browse the repository at this point in the history
Move newly-added tests from gocheck to testing/require.

* Move newly-added Webauthn tests out of gocheck
* Use t.TempDir and t.Run
* Make use of newIdentityService in recently merged code
  • Loading branch information
codingllama authored Aug 30, 2021
1 parent dba49bf commit 6561ea2
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 222 deletions.
9 changes: 0 additions & 9 deletions lib/services/local/services_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"github.com/gravitational/teleport/lib/backend/lite"
"github.com/gravitational/teleport/lib/services/suite"
"github.com/gravitational/teleport/lib/utils"

"github.com/jonboulle/clockwork"
"gopkg.in/check.v1"
)
Expand Down Expand Up @@ -129,14 +128,6 @@ func (s *ServicesSuite) TestU2FCRUD(c *check.C) {
s.suite.U2FCRUD(c)
}

func (s *ServicesSuite) TestWebauthnLocalAuthUpsert(c *check.C) {
s.suite.WebauthnLocalAuthUpsert(c)
}

func (s *ServicesSuite) TestWebauthnSessionDataCRUD(c *check.C) {
s.suite.WebauthnSessionDataCRUD(c)
}

func (s *ServicesSuite) TestSAMLCRUD(c *check.C) {
s.suite.SAMLCRUD(c)
}
Expand Down
264 changes: 221 additions & 43 deletions lib/services/local/users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,40 @@
* limitations under the License.
*/

package local
package local_test

import (
"context"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/backend/lite"
"github.com/gravitational/teleport/lib/services/local"
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"
"github.com/stretchr/testify/require"
)

func TestRecoveryCodesCRUD(t *testing.T) {
t.Parallel()
ctx := context.Background()
wantypes "github.com/gravitational/teleport/api/types/webauthn"
)

backend, err := lite.NewWithConfig(ctx, lite.Config{
Path: t.TempDir(),
Clock: clockwork.NewFakeClock(),
func newIdentityService(t *testing.T) (*local.IdentityService, clockwork.Clock) {
t.Helper()
clock := clockwork.NewFakeClock()
backend, err := lite.NewWithConfig(context.Background(), lite.Config{
Path: t.TempDir(),
PollStreamPeriod: 200 * time.Millisecond,
Clock: clock,
})
require.NoError(t, err)
return local.NewIdentityService(backend), clock
}

service := NewIdentityService(backend)
func TestRecoveryCodesCRUD(t *testing.T) {
t.Parallel()
ctx := context.Background()
identity, clock := newIdentityService(t)

// Create a recovery codes resource.
mockedCodes := []types.RecoveryCode{
Expand All @@ -51,15 +60,15 @@ func TestRecoveryCodesCRUD(t *testing.T) {
t.Parallel()
username := "someuser"

rc1, err := types.NewRecoveryCodes(mockedCodes, backend.Clock().Now(), username)
rc1, err := types.NewRecoveryCodes(mockedCodes, clock.Now(), username)
require.NoError(t, err)

// Test creation of codes.
err = service.UpsertRecoveryCodes(ctx, username, rc1)
err = identity.UpsertRecoveryCodes(ctx, username, rc1)
require.NoError(t, err)

// Test fetching of codes.
codes, err := service.GetRecoveryCodes(ctx, username)
codes, err := identity.GetRecoveryCodes(ctx, username)
require.NoError(t, err)
require.ElementsMatch(t, mockedCodes, codes.GetCodes())

Expand All @@ -69,15 +78,15 @@ func TestRecoveryCodesCRUD(t *testing.T) {
{HashedCode: []byte("new-code2")},
{HashedCode: []byte("new-code3")},
}
rc2, err := types.NewRecoveryCodes(newMockedCodes, backend.Clock().Now(), username)
rc2, err := types.NewRecoveryCodes(newMockedCodes, clock.Now(), username)
require.NoError(t, err)

// Test update of codes for same user.
err = service.UpsertRecoveryCodes(ctx, username, rc2)
err = identity.UpsertRecoveryCodes(ctx, username, rc2)
require.NoError(t, err)

// Test codes have been updated for same user.
codes, err = service.GetRecoveryCodes(ctx, username)
codes, err = identity.GetRecoveryCodes(ctx, username)
require.NoError(t, err)
require.ElementsMatch(t, newMockedCodes, codes.GetCodes())
})
Expand All @@ -89,67 +98,60 @@ func TestRecoveryCodesCRUD(t *testing.T) {
// Create a user.
userResource := &types.UserV2{}
userResource.SetName(username)
err := service.CreateUser(userResource)
err := identity.CreateUser(userResource)
require.NoError(t, err)

// Test codes exist for user.
rc1, err := types.NewRecoveryCodes(mockedCodes, backend.Clock().Now(), username)
rc1, err := types.NewRecoveryCodes(mockedCodes, clock.Now(), username)
require.NoError(t, err)
err = service.UpsertRecoveryCodes(ctx, username, rc1)
err = identity.UpsertRecoveryCodes(ctx, username, rc1)
require.NoError(t, err)
codes, err := service.GetRecoveryCodes(ctx, username)
codes, err := identity.GetRecoveryCodes(ctx, username)
require.NoError(t, err)
require.ElementsMatch(t, mockedCodes, codes.GetCodes())

// Test deletion of recovery code along with user.
err = service.DeleteUser(ctx, username)
err = identity.DeleteUser(ctx, username)
require.NoError(t, err)
_, err = service.GetRecoveryCodes(ctx, username)
_, err = identity.GetRecoveryCodes(ctx, username)
require.True(t, trace.IsNotFound(err))
})
}

func TestRecoveryAttemptsCRUD(t *testing.T) {
t.Parallel()
ctx := context.Background()

backend, err := lite.NewWithConfig(ctx, lite.Config{
Path: t.TempDir(),
Clock: clockwork.NewFakeClock(),
})
require.NoError(t, err)

service := NewIdentityService(backend)
identity, clock := newIdentityService(t)

// Predefine times for equality check.
time1 := backend.Clock().Now()
time2 := backend.Clock().Now().Add(2 * time.Minute)
time3 := backend.Clock().Now().Add(4 * time.Minute)
time1 := clock.Now()
time2 := time1.Add(2 * time.Minute)
time3 := time1.Add(4 * time.Minute)

t.Run("create, get, and delete recovery attempts", func(t *testing.T) {
t.Parallel()
username := "someuser"

// Test creation of recovery attempt.
err := service.CreateUserRecoveryAttempt(ctx, username, &types.RecoveryAttempt{Time: time3, Expires: time3})
err := identity.CreateUserRecoveryAttempt(ctx, username, &types.RecoveryAttempt{Time: time3, Expires: time3})
require.NoError(t, err)
err = service.CreateUserRecoveryAttempt(ctx, username, &types.RecoveryAttempt{Time: time1, Expires: time3})
err = identity.CreateUserRecoveryAttempt(ctx, username, &types.RecoveryAttempt{Time: time1, Expires: time3})
require.NoError(t, err)
err = service.CreateUserRecoveryAttempt(ctx, username, &types.RecoveryAttempt{Time: time2, Expires: time3})
err = identity.CreateUserRecoveryAttempt(ctx, username, &types.RecoveryAttempt{Time: time2, Expires: time3})
require.NoError(t, err)

// Test retrieving attempts sorted by oldest to latest.
attempts, err := service.GetUserRecoveryAttempts(ctx, username)
attempts, err := identity.GetUserRecoveryAttempts(ctx, username)
require.NoError(t, err)
require.Len(t, attempts, 3)
require.Equal(t, time1, attempts[0].Time)
require.Equal(t, time2, attempts[1].Time)
require.Equal(t, time3, attempts[2].Time)

// Test delete all recovery attempts.
err = service.DeleteUserRecoveryAttempts(ctx, username)
err = identity.DeleteUserRecoveryAttempts(ctx, username)
require.NoError(t, err)
attempts, err = service.GetUserRecoveryAttempts(ctx, username)
attempts, err = identity.GetUserRecoveryAttempts(ctx, username)
require.NoError(t, err)
require.Len(t, attempts, 0)
})
Expand All @@ -161,19 +163,195 @@ func TestRecoveryAttemptsCRUD(t *testing.T) {
// Create a user, to test deletion of recovery attempts with user.
userResource := &types.UserV2{}
userResource.SetName(username)
err := service.CreateUser(userResource)
err := identity.CreateUser(userResource)
require.NoError(t, err)

err = service.CreateUserRecoveryAttempt(ctx, username, &types.RecoveryAttempt{Time: time3, Expires: time3})
err = identity.CreateUserRecoveryAttempt(ctx, username, &types.RecoveryAttempt{Time: time3, Expires: time3})
require.NoError(t, err)
attempts, err := service.GetUserRecoveryAttempts(ctx, username)
attempts, err := identity.GetUserRecoveryAttempts(ctx, username)
require.NoError(t, err)
require.Len(t, attempts, 1)

err = service.DeleteUser(ctx, username)
err = identity.DeleteUser(ctx, username)
require.NoError(t, err)
attempts, err = service.GetUserRecoveryAttempts(ctx, username)
attempts, err = identity.GetUserRecoveryAttempts(ctx, username)
require.NoError(t, err)
require.Len(t, attempts, 0)
})
}

func TestIdentityService_UpsertWebauthnLocalAuth(t *testing.T) {
t.Parallel()
identity, _ := newIdentityService(t)

updateViaUser := func(ctx context.Context, user string, wal *types.WebauthnLocalAuth) error {
u, err := types.NewUser(user)
if err != nil {
return err
}
las := u.GetLocalAuth()
if las == nil {
las = &types.LocalAuthSecrets{}
}
las.Webauthn = wal
u.SetLocalAuth(las)

err = identity.UpsertUser(u)
return err
}
getViaUser := func(ctx context.Context, user string) (*types.WebauthnLocalAuth, error) {
u, err := identity.GetUser(user, true /* withSecrets */)
if err != nil {
return nil, err
}
return u.GetLocalAuth().Webauthn, nil
}

// Create a user to begin with.
const name = "llama"
user, err := types.NewUser(name)
require.NoError(t, err)
err = identity.UpsertUser(user)
require.NoError(t, err)

// Try a few empty reads.
ctx := context.Background()
_, err = identity.GetUser(name, true /* withSecrets */)
require.NoError(t, err) // User read should be fine.
_, err = identity.GetWebauthnLocalAuth(ctx, name)
require.True(t, trace.IsNotFound(err)) // Direct WAL read should fail.

// Try a few invalid updates.
badWAL := &types.WebauthnLocalAuth{} // missing UserID
err = identity.UpsertWebauthnLocalAuth(ctx, name, badWAL)
require.True(t, trace.IsBadParameter(err))
user.SetLocalAuth(&types.LocalAuthSecrets{Webauthn: badWAL})
err = identity.UpdateUser(ctx, user)
require.True(t, trace.IsBadParameter(err))

// Update/Read tests.
tests := []struct {
name string
user string
wal *types.WebauthnLocalAuth
update func(context.Context, string, *types.WebauthnLocalAuth) error
get func(context.Context, string) (*types.WebauthnLocalAuth, error)
}{
{
name: "OK: Create WAL directly",
user: "llama",
wal: &types.WebauthnLocalAuth{UserID: []byte("webauthn user ID for llama")},
update: identity.UpsertWebauthnLocalAuth,
get: identity.GetWebauthnLocalAuth,
},
{
name: "OK: Update WAL directly",
user: "llama", // same as above
wal: &types.WebauthnLocalAuth{UserID: []byte("another ID")},
update: identity.UpsertWebauthnLocalAuth,
get: identity.GetWebauthnLocalAuth,
},
{
name: "OK: Create WAL via user",
user: "alpaca", // new user
wal: &types.WebauthnLocalAuth{UserID: []byte("webauthn user ID for alpaca")},
update: updateViaUser,
get: getViaUser,
},
{
name: "OK: Update WAL via user",
user: "alpaca", // same as above
wal: &types.WebauthnLocalAuth{UserID: []byte("some other ID")},
update: updateViaUser,
get: getViaUser,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.update(ctx, test.name, test.wal)
require.NoError(t, err)

want := test.wal
got, err := test.get(ctx, test.name)
require.NoError(t, err)
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("WebauthnLocalAuth mismatch (-want +got):\n%s", diff)
}
})
}
}

func TestIdentityService_WebauthnSessionDataCRUD(t *testing.T) {
t.Parallel()
identity, _ := newIdentityService(t)

const user1 = "llama"
const user2 = "alpaca"
// Prepare a few different objects so we can assert that both "user" and
// "session" key components are used correctly.
user1Reg := &wantypes.SessionData{
Challenge: []byte("challenge1-reg"),
UserId: []byte("llamaid"),
}
user1Login := &wantypes.SessionData{
Challenge: []byte("challenge1-login"),
UserId: []byte("llamaid"),
AllowCredentials: [][]byte{[]byte("cred1"), []byte("cred2")},
}
user2Login := &wantypes.SessionData{
Challenge: []byte("challenge2"),
UserId: []byte("alpacaid"),
}

// Usually there are only 2 sessions for each user: login and registration.
const registerSession = "register"
const loginSession = "login"
params := []struct {
user, session string
sd *wantypes.SessionData
}{
{user: user1, session: registerSession, sd: user1Reg},
{user: user1, session: loginSession, sd: user1Login},
{user: user2, session: loginSession, sd: user2Login},
}

// Verify upsert/create.
ctx := context.Background()
for _, p := range params {
err := identity.UpsertWebauthnSessionData(ctx, p.user, p.session, p.sd)
require.NoError(t, err)
}

// Verify read.
for _, p := range params {
got, err := identity.GetWebauthnSessionData(ctx, p.user, p.session)
require.NoError(t, err)
if diff := cmp.Diff(p.sd, got); diff != "" {
t.Fatalf("GetWebauthnSessionData() mismatch (-want +got):\n%s", diff)
}
}

// Verify upsert/update.
user1Reg = &wantypes.SessionData{
Challenge: []byte("challenge1reg--another"),
UserId: []byte("llamaid"),
}
err := identity.UpsertWebauthnSessionData(ctx, user1, registerSession, user1Reg)
require.NoError(t, err)
got, err := identity.GetWebauthnSessionData(ctx, user1, registerSession)
require.NoError(t, err)
if diff := cmp.Diff(user1Reg, got); diff != "" {
t.Fatalf("GetWebauthnSessionData() mismatch (-want +got):\n%s", diff)
}

// Verify deletion.
err = identity.DeleteWebauthnSessionData(ctx, user1, registerSession)
require.NoError(t, err)
_, err = identity.GetWebauthnSessionData(ctx, user1, registerSession)
require.True(t, trace.IsNotFound(err))
params = params[1:] // Remove user1/register from params
for _, p := range params {
_, err := identity.GetWebauthnSessionData(ctx, p.user, p.session)
require.NoError(t, err) // Other keys preserved
}
}
Loading

0 comments on commit 6561ea2

Please # to comment.