2022-03-03 11:20:43 +00:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"testing"
|
|
|
|
|
2022-03-03 23:46:38 +00:00
|
|
|
"github.com/go-webauthn/webauthn/protocol"
|
2023-02-19 01:40:57 +00:00
|
|
|
"github.com/golang/mock/gomock"
|
2022-03-03 11:20:43 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
|
|
|
"github.com/authelia/authelia/v4/internal/mocks"
|
2022-03-06 05:47:40 +00:00
|
|
|
"github.com/authelia/authelia/v4/internal/model"
|
2023-02-19 01:40:57 +00:00
|
|
|
"github.com/authelia/authelia/v4/internal/random"
|
2022-03-03 11:20:43 +00:00
|
|
|
"github.com/authelia/authelia/v4/internal/session"
|
|
|
|
)
|
|
|
|
|
2023-04-11 04:40:09 +00:00
|
|
|
func TestWebAuthnGetUser(t *testing.T) {
|
2022-03-03 11:20:43 +00:00
|
|
|
ctx := mocks.NewMockAutheliaCtx(t)
|
|
|
|
|
|
|
|
userSession := session.UserSession{
|
|
|
|
Username: "john",
|
|
|
|
DisplayName: "John Smith",
|
|
|
|
}
|
|
|
|
|
2023-02-19 01:40:57 +00:00
|
|
|
ctx.StorageMock.EXPECT().
|
2023-04-11 04:40:09 +00:00
|
|
|
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
|
|
|
|
Return(&model.WebAuthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
|
2023-02-19 01:40:57 +00:00
|
|
|
|
2023-04-11 04:40:09 +00:00
|
|
|
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebAuthnDevice{
|
2022-03-03 11:20:43 +00:00
|
|
|
{
|
|
|
|
ID: 1,
|
2023-02-11 15:47:03 +00:00
|
|
|
RPID: "example.com",
|
2022-03-03 11:20:43 +00:00
|
|
|
Username: "john",
|
2023-02-16 19:40:40 +00:00
|
|
|
Description: "Primary",
|
2022-03-06 05:47:40 +00:00
|
|
|
KID: model.NewBase64([]byte("abc123")),
|
2022-03-03 11:20:43 +00:00
|
|
|
AttestationType: "fido-u2f",
|
|
|
|
PublicKey: []byte("data"),
|
|
|
|
SignCount: 0,
|
|
|
|
CloneWarning: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ID: 2,
|
|
|
|
RPID: "example.com",
|
|
|
|
Username: "john",
|
2023-02-16 19:40:40 +00:00
|
|
|
Description: "Secondary",
|
2022-03-06 05:47:40 +00:00
|
|
|
KID: model.NewBase64([]byte("123abc")),
|
2022-03-03 11:20:43 +00:00
|
|
|
AttestationType: "packed",
|
|
|
|
Transport: "usb,nfc",
|
|
|
|
PublicKey: []byte("data"),
|
|
|
|
SignCount: 100,
|
|
|
|
CloneWarning: false,
|
|
|
|
},
|
|
|
|
}, nil)
|
|
|
|
|
2023-04-11 04:40:09 +00:00
|
|
|
user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
|
2022-03-03 11:20:43 +00:00
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, user)
|
|
|
|
|
2023-02-19 01:40:57 +00:00
|
|
|
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])
|
|
|
|
}
|
|
|
|
|
2023-04-11 04:40:09 +00:00
|
|
|
func TestWebAuthnGetNewUser(t *testing.T) {
|
2023-02-19 01:40:57 +00:00
|
|
|
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().
|
2023-04-11 04:40:09 +00:00
|
|
|
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
|
2023-02-19 01:40:57 +00:00
|
|
|
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().
|
2023-04-11 04:40:09 +00:00
|
|
|
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"}).
|
2023-02-19 01:40:57 +00:00
|
|
|
Return(nil),
|
2023-04-11 04:40:09 +00:00
|
|
|
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebAuthnDevice{
|
2023-02-19 01:40:57 +00:00
|
|
|
{
|
|
|
|
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),
|
|
|
|
)
|
|
|
|
|
2023-04-11 04:40:09 +00:00
|
|
|
user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
|
2023-02-19 01:40:57 +00:00
|
|
|
|
|
|
|
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())
|
2022-03-03 11:20:43 +00:00
|
|
|
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)
|
2023-02-11 15:47:03 +00:00
|
|
|
assert.Equal(t, "example.com", user.Devices[0].RPID)
|
2022-03-03 11:20:43 +00:00
|
|
|
assert.Equal(t, "john", user.Devices[0].Username)
|
2023-02-16 19:40:40 +00:00
|
|
|
assert.Equal(t, "Primary", user.Devices[0].Description)
|
2022-03-03 11:20:43 +00:00
|
|
|
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)
|
2023-02-12 19:30:19 +00:00
|
|
|
assert.Equal(t, "abc123", string(descriptors[0].CredentialID))
|
2022-03-03 11:20:43 +00:00
|
|
|
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)
|
2023-02-16 19:40:40 +00:00
|
|
|
assert.Equal(t, "Secondary", user.Devices[1].Description)
|
2022-03-03 11:20:43 +00:00
|
|
|
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)
|
2023-02-12 19:30:19 +00:00
|
|
|
assert.Equal(t, "123abc", string(descriptors[1].CredentialID))
|
2022-03-03 11:20:43 +00:00
|
|
|
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])
|
|
|
|
}
|
|
|
|
|
2023-04-11 04:40:09 +00:00
|
|
|
func TestWebAuthnGetUserWithoutDisplayName(t *testing.T) {
|
2022-03-03 11:20:43 +00:00
|
|
|
ctx := mocks.NewMockAutheliaCtx(t)
|
|
|
|
|
|
|
|
userSession := session.UserSession{
|
|
|
|
Username: "john",
|
|
|
|
}
|
|
|
|
|
2023-02-19 01:40:57 +00:00
|
|
|
ctx.StorageMock.EXPECT().
|
2023-04-11 04:40:09 +00:00
|
|
|
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
|
|
|
|
Return(&model.WebAuthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
|
2023-02-19 01:40:57 +00:00
|
|
|
|
2023-04-11 04:40:09 +00:00
|
|
|
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebAuthnDevice{
|
2022-03-03 11:20:43 +00:00
|
|
|
{
|
|
|
|
ID: 1,
|
2023-02-11 15:47:03 +00:00
|
|
|
RPID: "example.com",
|
2022-03-03 11:20:43 +00:00
|
|
|
Username: "john",
|
2023-02-16 19:40:40 +00:00
|
|
|
Description: "Primary",
|
2022-03-06 05:47:40 +00:00
|
|
|
KID: model.NewBase64([]byte("abc123")),
|
2022-03-03 11:20:43 +00:00
|
|
|
AttestationType: "fido-u2f",
|
|
|
|
PublicKey: []byte("data"),
|
|
|
|
SignCount: 0,
|
|
|
|
CloneWarning: false,
|
|
|
|
},
|
|
|
|
}, nil)
|
|
|
|
|
2023-04-11 04:40:09 +00:00
|
|
|
user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
|
2022-03-03 11:20:43 +00:00
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, user)
|
|
|
|
|
|
|
|
assert.Equal(t, "john", user.WebAuthnDisplayName())
|
|
|
|
assert.Equal(t, "john", user.DisplayName)
|
|
|
|
}
|
|
|
|
|
2023-04-11 04:40:09 +00:00
|
|
|
func TestWebAuthnGetUserWithErr(t *testing.T) {
|
2022-03-03 11:20:43 +00:00
|
|
|
ctx := mocks.NewMockAutheliaCtx(t)
|
|
|
|
|
|
|
|
userSession := session.UserSession{
|
|
|
|
Username: "john",
|
|
|
|
}
|
|
|
|
|
2023-02-19 01:40:57 +00:00
|
|
|
ctx.StorageMock.EXPECT().
|
2023-04-11 04:40:09 +00:00
|
|
|
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
|
|
|
|
Return(&model.WebAuthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
|
2023-02-19 01:40:57 +00:00
|
|
|
|
|
|
|
ctx.StorageMock.EXPECT().
|
2023-04-11 04:40:09 +00:00
|
|
|
LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").
|
2023-02-19 01:40:57 +00:00
|
|
|
Return(nil, errors.New("not found"))
|
2022-03-03 11:20:43 +00:00
|
|
|
|
2023-04-11 04:40:09 +00:00
|
|
|
user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
|
2022-03-03 11:20:43 +00:00
|
|
|
|
|
|
|
assert.EqualError(t, err, "not found")
|
|
|
|
assert.Nil(t, user)
|
|
|
|
}
|
|
|
|
|
2023-04-11 04:40:09 +00:00
|
|
|
func TestWebAuthnNewWebAuthnShouldReturnErrWhenHeadersNotAvailable(t *testing.T) {
|
2022-03-03 11:20:43 +00:00
|
|
|
ctx := mocks.NewMockAutheliaCtx(t)
|
2023-01-12 10:57:44 +00:00
|
|
|
ctx.Ctx.Request.Header.Del("X-Forwarded-Host")
|
2022-03-03 11:20:43 +00:00
|
|
|
|
2023-04-11 04:40:09 +00:00
|
|
|
w, err := newWebAuthn(ctx.Ctx)
|
2022-03-03 11:20:43 +00:00
|
|
|
|
|
|
|
assert.Nil(t, w)
|
2023-01-25 09:36:40 +00:00
|
|
|
assert.EqualError(t, err, "missing required X-Forwarded-Host header")
|
2022-03-03 11:20:43 +00:00
|
|
|
}
|
|
|
|
|
2023-04-11 04:40:09 +00:00
|
|
|
func TestWebAuthnNewWebAuthnShouldReturnErrWhenWebAuthnNotConfigured(t *testing.T) {
|
2022-03-03 11:20:43 +00:00
|
|
|
ctx := mocks.NewMockAutheliaCtx(t)
|
|
|
|
|
|
|
|
ctx.Ctx.Request.Header.Set("X-Forwarded-Host", "example.com")
|
|
|
|
ctx.Ctx.Request.Header.Set("X-Forwarded-URI", "/")
|
|
|
|
ctx.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
|
|
|
|
|
2023-04-11 04:40:09 +00:00
|
|
|
w, err := newWebAuthn(ctx.Ctx)
|
2022-03-03 11:20:43 +00:00
|
|
|
|
|
|
|
assert.Nil(t, w)
|
2023-02-19 01:11:33 +00:00
|
|
|
assert.EqualError(t, err, "error occurred validating the configuration: the field 'RPDisplayName' must be configured but it is empty")
|
2022-03-03 11:20:43 +00:00
|
|
|
}
|