diff --git a/internal/handlers/webauthn.go b/internal/handlers/webauthn.go index 32e48ef1f..cff51284b 100644 --- a/internal/handlers/webauthn.go +++ b/internal/handlers/webauthn.go @@ -12,7 +12,7 @@ import ( "github.com/authelia/authelia/v4/internal/random" ) -func getWebauthnUserByRPID(ctx *middlewares.AutheliaCtx, username, description string, rpid string) (user *model.WebauthnUser, err error) { +func getWebauthnUserByRPID(ctx *middlewares.AutheliaCtx, username, displayname string, rpid string) (user *model.WebauthnUser, err error) { if user, err = ctx.Providers.StorageProvider.LoadWebauthnUser(ctx, rpid, username); err != nil { return nil, err } @@ -22,12 +22,14 @@ func getWebauthnUserByRPID(ctx *middlewares.AutheliaCtx, username, description s RPID: rpid, Username: username, UserID: ctx.Providers.Random.StringCustom(64, random.CharSetASCII), - DisplayName: description, + DisplayName: displayname, } if err = ctx.Providers.StorageProvider.SaveWebauthnUser(ctx, *user); err != nil { return nil, err } + } else { + user.DisplayName = displayname } if user.DisplayName == "" { diff --git a/internal/handlers/webauthn_test.go b/internal/handlers/webauthn_test.go index 3e013a3a7..d5864dadb 100644 --- a/internal/handlers/webauthn_test.go +++ b/internal/handlers/webauthn_test.go @@ -5,11 +5,13 @@ import ( "testing" "github.com/go-webauthn/webauthn/protocol" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/authelia/authelia/v4/internal/mocks" "github.com/authelia/authelia/v4/internal/model" + "github.com/authelia/authelia/v4/internal/random" "github.com/authelia/authelia/v4/internal/session" ) @@ -21,6 +23,10 @@ func TestWebauthnGetUser(t *testing.T) { DisplayName: "John Smith", } + ctx.StorageMock.EXPECT(). + LoadWebauthnUser(ctx.Ctx, "example.com", "john"). + Return(&model.WebauthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil) + ctx.StorageMock.EXPECT().LoadWebauthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebauthnDevice{ { ID: 1, @@ -52,7 +58,107 @@ func TestWebauthnGetUser(t *testing.T) { require.NoError(t, err) require.NotNil(t, user) - assert.Equal(t, []byte("john"), user.WebAuthnID()) + assert.Equal(t, []byte("john123"), user.WebAuthnID()) + assert.Equal(t, "john", user.WebAuthnName()) + assert.Equal(t, "john", user.Username) + + assert.Equal(t, "", user.WebAuthnIcon()) + + assert.Equal(t, "John Smith", user.WebAuthnDisplayName()) + assert.Equal(t, "John Smith", user.DisplayName) + + require.Len(t, user.Devices, 2) + + assert.Equal(t, 1, user.Devices[0].ID) + assert.Equal(t, "example.com", user.Devices[0].RPID) + assert.Equal(t, "john", user.Devices[0].Username) + assert.Equal(t, "Primary", user.Devices[0].Description) + assert.Equal(t, "", user.Devices[0].Transport) + assert.Equal(t, "fido-u2f", user.Devices[0].AttestationType) + assert.Equal(t, []byte("data"), user.Devices[0].PublicKey) + assert.Equal(t, uint32(0), user.Devices[0].SignCount) + assert.False(t, user.Devices[0].CloneWarning) + + descriptors := user.WebAuthnCredentialDescriptors() + assert.Equal(t, "fido-u2f", descriptors[0].AttestationType) + assert.Equal(t, "abc123", string(descriptors[0].CredentialID)) + assert.Equal(t, protocol.PublicKeyCredentialType, descriptors[0].Type) + + assert.Len(t, descriptors[0].Transport, 0) + + assert.Equal(t, 2, user.Devices[1].ID) + assert.Equal(t, "example.com", user.Devices[1].RPID) + assert.Equal(t, "john", user.Devices[1].Username) + assert.Equal(t, "Secondary", user.Devices[1].Description) + assert.Equal(t, "usb,nfc", user.Devices[1].Transport) + assert.Equal(t, "packed", user.Devices[1].AttestationType) + assert.Equal(t, []byte("data"), user.Devices[1].PublicKey) + assert.Equal(t, uint32(100), user.Devices[1].SignCount) + assert.False(t, user.Devices[1].CloneWarning) + + assert.Equal(t, "packed", descriptors[1].AttestationType) + assert.Equal(t, "123abc", string(descriptors[1].CredentialID)) + assert.Equal(t, protocol.PublicKeyCredentialType, descriptors[1].Type) + + assert.Len(t, descriptors[1].Transport, 2) + assert.Equal(t, protocol.AuthenticatorTransport("usb"), descriptors[1].Transport[0]) + assert.Equal(t, protocol.AuthenticatorTransport("nfc"), descriptors[1].Transport[1]) +} + +func TestWebauthnGetNewUser(t *testing.T) { + ctx := mocks.NewMockAutheliaCtx(t) + + // Use the random mock. + ctx.Ctx.Providers.Random = ctx.RandomMock + + userSession := session.UserSession{ + Username: "john", + DisplayName: "John Smith", + } + + gomock.InOrder( + ctx.StorageMock.EXPECT(). + LoadWebauthnUser(ctx.Ctx, "example.com", "john"). + Return(nil, nil), + ctx.RandomMock.EXPECT(). + StringCustom(64, random.CharSetASCII). + Return("=ckBRe.%fp{w#K[qw4)AWMZrAP)(z3NUt5n3g?;>'^Rp>+eE4z>[^.<3?&n;LM#w"), + ctx.StorageMock.EXPECT(). + SaveWebauthnUser(ctx.Ctx, model.WebauthnUser{RPID: "example.com", Username: "john", DisplayName: "John Smith", UserID: "=ckBRe.%fp{w#K[qw4)AWMZrAP)(z3NUt5n3g?;>'^Rp>+eE4z>[^.<3?&n;LM#w"}). + Return(nil), + ctx.StorageMock.EXPECT().LoadWebauthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebauthnDevice{ + { + ID: 1, + RPID: "example.com", + Username: "john", + Description: "Primary", + KID: model.NewBase64([]byte("abc123")), + AttestationType: "fido-u2f", + PublicKey: []byte("data"), + SignCount: 0, + CloneWarning: false, + }, + { + ID: 2, + RPID: "example.com", + Username: "john", + Description: "Secondary", + KID: model.NewBase64([]byte("123abc")), + AttestationType: "packed", + Transport: "usb,nfc", + PublicKey: []byte("data"), + SignCount: 100, + CloneWarning: false, + }, + }, nil), + ) + + user, err := getWebauthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com") + + require.NoError(t, err) + require.NotNil(t, user) + + assert.Equal(t, []byte("=ckBRe.%fp{w#K[qw4)AWMZrAP)(z3NUt5n3g?;>'^Rp>+eE4z>[^.<3?&n;LM#w"), user.WebAuthnID()) assert.Equal(t, "john", user.WebAuthnName()) assert.Equal(t, "john", user.Username) @@ -106,6 +212,10 @@ func TestWebauthnGetUserWithoutDisplayName(t *testing.T) { Username: "john", } + ctx.StorageMock.EXPECT(). + LoadWebauthnUser(ctx.Ctx, "example.com", "john"). + Return(&model.WebauthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil) + ctx.StorageMock.EXPECT().LoadWebauthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebauthnDevice{ { ID: 1, @@ -136,7 +246,13 @@ func TestWebauthnGetUserWithErr(t *testing.T) { Username: "john", } - ctx.StorageMock.EXPECT().LoadWebauthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return(nil, errors.New("not found")) + ctx.StorageMock.EXPECT(). + LoadWebauthnUser(ctx.Ctx, "example.com", "john"). + Return(&model.WebauthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil) + + ctx.StorageMock.EXPECT(). + LoadWebauthnDevicesByUsername(ctx.Ctx, "example.com", "john"). + Return(nil, errors.New("not found")) user, err := getWebauthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")