2019-04-25 04:52:08 +07:00
|
|
|
package middlewares_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2022-07-18 07:56:09 +07:00
|
|
|
"net/mail"
|
2019-04-25 04:52:08 +07:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2021-08-04 04:38:07 +07:00
|
|
|
"github.com/golang-jwt/jwt/v4"
|
2019-04-25 04:52:08 +07:00
|
|
|
"github.com/golang/mock/gomock"
|
2021-12-04 11:48:22 +07:00
|
|
|
"github.com/google/uuid"
|
2019-04-25 04:52:08 +07:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/suite"
|
2020-04-05 19:37:21 +07:00
|
|
|
|
2021-08-11 08:04:35 +07:00
|
|
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
|
|
|
"github.com/authelia/authelia/v4/internal/mocks"
|
2022-03-06 12:47:40 +07:00
|
|
|
"github.com/authelia/authelia/v4/internal/model"
|
2021-08-11 08:04:35 +07:00
|
|
|
"github.com/authelia/authelia/v4/internal/session"
|
2019-04-25 04:52:08 +07:00
|
|
|
)
|
|
|
|
|
2020-05-02 23:20:40 +07:00
|
|
|
const testJWTSecret = "abc"
|
|
|
|
|
2019-04-25 04:52:08 +07:00
|
|
|
func newArgs(retriever func(ctx *middlewares.AutheliaCtx) (*session.Identity, error)) middlewares.IdentityVerificationStartArgs {
|
|
|
|
return middlewares.IdentityVerificationStartArgs{
|
|
|
|
ActionClaim: "Claim",
|
|
|
|
MailButtonContent: "Register",
|
|
|
|
MailTitle: "Title",
|
|
|
|
TargetEndpoint: "/target",
|
|
|
|
IdentityRetrieverFunc: retriever,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultRetriever(ctx *middlewares.AutheliaCtx) (*session.Identity, error) {
|
|
|
|
return &session.Identity{
|
|
|
|
Username: "john",
|
|
|
|
Email: "john@example.com",
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestShouldFailStartingProcessIfUserHasNoEmailAddress(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
|
|
|
|
|
|
|
retriever := func(ctx *middlewares.AutheliaCtx) (*session.Identity, error) {
|
|
|
|
return nil, fmt.Errorf("User does not have any email")
|
|
|
|
}
|
|
|
|
|
2022-01-21 06:46:13 +07:00
|
|
|
middlewares.IdentityVerificationStart(newArgs(retriever), nil)(mock.Ctx)
|
2019-04-25 04:52:08 +07:00
|
|
|
|
|
|
|
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
|
|
|
assert.Equal(t, "User does not have any email", mock.Hook.LastEntry().Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestShouldFailIfJWTCannotBeSaved(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
|
|
|
|
2020-05-02 23:20:40 +07:00
|
|
|
mock.Ctx.Configuration.JWTSecret = testJWTSecret
|
2019-04-25 04:52:08 +07:00
|
|
|
|
2021-12-01 19:11:29 +07:00
|
|
|
mock.StorageMock.EXPECT().
|
2021-11-23 16:45:38 +07:00
|
|
|
SaveIdentityVerification(mock.Ctx, gomock.Any()).
|
2019-04-25 04:52:08 +07:00
|
|
|
Return(fmt.Errorf("cannot save"))
|
|
|
|
|
|
|
|
args := newArgs(defaultRetriever)
|
2022-01-21 06:46:13 +07:00
|
|
|
middlewares.IdentityVerificationStart(args, nil)(mock.Ctx)
|
2019-04-25 04:52:08 +07:00
|
|
|
|
|
|
|
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
|
|
|
assert.Equal(t, "cannot save", mock.Hook.LastEntry().Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestShouldFailSendingAnEmail(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
|
|
|
|
2020-05-02 23:20:40 +07:00
|
|
|
mock.Ctx.Configuration.JWTSecret = testJWTSecret
|
2020-02-13 09:12:37 +07:00
|
|
|
mock.Ctx.Request.Header.Add("X-Forwarded-Proto", "http")
|
|
|
|
mock.Ctx.Request.Header.Add("X-Forwarded-Host", "host")
|
2019-04-25 04:52:08 +07:00
|
|
|
|
2021-12-01 19:11:29 +07:00
|
|
|
mock.StorageMock.EXPECT().
|
2021-11-23 16:45:38 +07:00
|
|
|
SaveIdentityVerification(mock.Ctx, gomock.Any()).
|
2019-04-25 04:52:08 +07:00
|
|
|
Return(nil)
|
|
|
|
|
|
|
|
mock.NotifierMock.EXPECT().
|
2022-07-18 07:56:09 +07:00
|
|
|
Send(gomock.Eq(mail.Address{Address: "john@example.com"}), gomock.Eq("Title"), gomock.Any(), gomock.Any()).
|
2019-04-25 04:52:08 +07:00
|
|
|
Return(fmt.Errorf("no notif"))
|
|
|
|
|
|
|
|
args := newArgs(defaultRetriever)
|
2022-01-21 06:46:13 +07:00
|
|
|
middlewares.IdentityVerificationStart(args, nil)(mock.Ctx)
|
2019-04-25 04:52:08 +07:00
|
|
|
|
|
|
|
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
|
|
|
assert.Equal(t, "no notif", mock.Hook.LastEntry().Message)
|
|
|
|
}
|
|
|
|
|
2020-02-13 09:12:37 +07:00
|
|
|
func TestShouldFailWhenXForwardedHostHeaderIsMissing(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
|
|
|
|
2020-05-02 23:20:40 +07:00
|
|
|
mock.Ctx.Configuration.JWTSecret = testJWTSecret
|
2020-02-13 09:12:37 +07:00
|
|
|
mock.Ctx.Request.Header.Add("X-Forwarded-Proto", "http")
|
|
|
|
|
2021-12-01 19:11:29 +07:00
|
|
|
mock.StorageMock.EXPECT().
|
2021-11-23 16:45:38 +07:00
|
|
|
SaveIdentityVerification(mock.Ctx, gomock.Any()).
|
2020-02-13 09:12:37 +07:00
|
|
|
Return(nil)
|
|
|
|
|
|
|
|
args := newArgs(defaultRetriever)
|
2022-01-21 06:46:13 +07:00
|
|
|
middlewares.IdentityVerificationStart(args, nil)(mock.Ctx)
|
2020-02-13 09:12:37 +07:00
|
|
|
|
|
|
|
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
2020-08-21 08:15:20 +07:00
|
|
|
assert.Equal(t, "Missing header X-Forwarded-Host", mock.Hook.LastEntry().Message)
|
2020-02-13 09:12:37 +07:00
|
|
|
}
|
|
|
|
|
2020-01-21 07:10:00 +07:00
|
|
|
func TestShouldSucceedIdentityVerificationStartProcess(t *testing.T) {
|
2019-04-25 04:52:08 +07:00
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
|
2020-05-02 23:20:40 +07:00
|
|
|
mock.Ctx.Configuration.JWTSecret = testJWTSecret
|
2020-02-13 09:12:37 +07:00
|
|
|
mock.Ctx.Request.Header.Add("X-Forwarded-Proto", "http")
|
|
|
|
mock.Ctx.Request.Header.Add("X-Forwarded-Host", "host")
|
2019-04-25 04:52:08 +07:00
|
|
|
|
2021-12-01 19:11:29 +07:00
|
|
|
mock.StorageMock.EXPECT().
|
2021-11-23 16:45:38 +07:00
|
|
|
SaveIdentityVerification(mock.Ctx, gomock.Any()).
|
2019-04-25 04:52:08 +07:00
|
|
|
Return(nil)
|
|
|
|
|
|
|
|
mock.NotifierMock.EXPECT().
|
2022-07-18 07:56:09 +07:00
|
|
|
Send(gomock.Eq(mail.Address{Address: "john@example.com"}), gomock.Eq("Title"), gomock.Any(), gomock.Any()).
|
2019-04-25 04:52:08 +07:00
|
|
|
Return(nil)
|
|
|
|
|
|
|
|
args := newArgs(defaultRetriever)
|
2022-01-21 06:46:13 +07:00
|
|
|
middlewares.IdentityVerificationStart(args, nil)(mock.Ctx)
|
2019-04-25 04:52:08 +07:00
|
|
|
|
|
|
|
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
2020-08-21 09:16:23 +07:00
|
|
|
|
|
|
|
defer mock.Close()
|
2019-04-25 04:52:08 +07:00
|
|
|
}
|
|
|
|
|
2020-05-02 12:06:39 +07:00
|
|
|
// Test Finish process.
|
2019-04-25 04:52:08 +07:00
|
|
|
type IdentityVerificationFinishProcess struct {
|
|
|
|
suite.Suite
|
|
|
|
|
|
|
|
mock *mocks.MockAutheliaCtx
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IdentityVerificationFinishProcess) SetupTest() {
|
|
|
|
s.mock = mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
|
2020-05-02 23:20:40 +07:00
|
|
|
s.mock.Ctx.Configuration.JWTSecret = testJWTSecret
|
2019-04-25 04:52:08 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IdentityVerificationFinishProcess) TearDownTest() {
|
|
|
|
s.mock.Close()
|
|
|
|
}
|
|
|
|
|
2022-03-06 12:47:40 +07:00
|
|
|
func createToken(ctx *mocks.MockAutheliaCtx, username, action string, expiresAt time.Time) (data string, verification model.IdentityVerification) {
|
|
|
|
verification = model.NewIdentityVerification(uuid.New(), username, action, ctx.Ctx.RemoteIP())
|
2021-11-30 13:58:21 +07:00
|
|
|
|
|
|
|
verification.ExpiresAt = expiresAt
|
|
|
|
|
|
|
|
claims := verification.ToIdentityVerificationClaim()
|
|
|
|
|
2019-04-25 04:52:08 +07:00
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
2021-12-03 07:04:11 +07:00
|
|
|
ss, _ := token.SignedString([]byte(ctx.Ctx.Configuration.JWTSecret))
|
2020-05-06 02:35:32 +07:00
|
|
|
|
2021-11-30 13:58:21 +07:00
|
|
|
return ss, verification
|
2019-04-25 04:52:08 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
func next(ctx *middlewares.AutheliaCtx, username string) {}
|
|
|
|
|
|
|
|
func newFinishArgs() middlewares.IdentityVerificationFinishArgs {
|
|
|
|
return middlewares.IdentityVerificationFinishArgs{
|
|
|
|
ActionClaim: "EXP_ACTION",
|
|
|
|
IsTokenUserValidFunc: func(ctx *middlewares.AutheliaCtx, username string) bool { return true },
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IdentityVerificationFinishProcess) TestShouldFailIfJSONBodyIsMalformed() {
|
|
|
|
middlewares.IdentityVerificationFinish(newFinishArgs(), next)(s.mock.Ctx)
|
|
|
|
|
|
|
|
s.mock.Assert200KO(s.T(), "Operation failed")
|
|
|
|
assert.Equal(s.T(), "unexpected end of JSON input", s.mock.Hook.LastEntry().Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IdentityVerificationFinishProcess) TestShouldFailIfTokenIsNotProvided() {
|
|
|
|
s.mock.Ctx.Request.SetBodyString("{}")
|
|
|
|
middlewares.IdentityVerificationFinish(newFinishArgs(), next)(s.mock.Ctx)
|
|
|
|
|
|
|
|
s.mock.Assert200KO(s.T(), "Operation failed")
|
|
|
|
assert.Equal(s.T(), "No token provided", s.mock.Hook.LastEntry().Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IdentityVerificationFinishProcess) TestShouldFailIfTokenIsNotFoundInDB() {
|
2021-12-03 07:04:11 +07:00
|
|
|
token, verification := createToken(s.mock, "john", "Login",
|
2021-11-30 13:58:21 +07:00
|
|
|
time.Now().Add(1*time.Minute))
|
|
|
|
|
|
|
|
s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
|
2019-04-25 04:52:08 +07:00
|
|
|
|
2021-12-01 19:11:29 +07:00
|
|
|
s.mock.StorageMock.EXPECT().
|
2021-11-30 13:58:21 +07:00
|
|
|
FindIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String())).
|
2019-04-25 04:52:08 +07:00
|
|
|
Return(false, nil)
|
|
|
|
|
|
|
|
middlewares.IdentityVerificationFinish(newFinishArgs(), next)(s.mock.Ctx)
|
|
|
|
|
|
|
|
s.mock.Assert200KO(s.T(), "The identity verification token has already been used")
|
|
|
|
assert.Equal(s.T(), "Token is not in DB, it might have already been used", s.mock.Hook.LastEntry().Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IdentityVerificationFinishProcess) TestShouldFailIfTokenIsInvalid() {
|
|
|
|
s.mock.Ctx.Request.SetBodyString("{\"token\":\"abc\"}")
|
|
|
|
|
|
|
|
middlewares.IdentityVerificationFinish(newFinishArgs(), next)(s.mock.Ctx)
|
|
|
|
|
|
|
|
s.mock.Assert200KO(s.T(), "Operation failed")
|
|
|
|
assert.Equal(s.T(), "Cannot parse token", s.mock.Hook.LastEntry().Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IdentityVerificationFinishProcess) TestShouldFailIfTokenExpired() {
|
|
|
|
args := newArgs(defaultRetriever)
|
2021-12-03 07:04:11 +07:00
|
|
|
token, _ := createToken(s.mock, "john", args.ActionClaim,
|
2019-04-25 04:52:08 +07:00
|
|
|
time.Now().Add(-1*time.Minute))
|
|
|
|
s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
|
|
|
|
|
|
|
|
middlewares.IdentityVerificationFinish(newFinishArgs(), next)(s.mock.Ctx)
|
|
|
|
|
|
|
|
s.mock.Assert200KO(s.T(), "The identity verification token has expired")
|
|
|
|
assert.Equal(s.T(), "Token expired", s.mock.Hook.LastEntry().Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IdentityVerificationFinishProcess) TestShouldFailForWrongAction() {
|
2021-12-03 07:04:11 +07:00
|
|
|
token, verification := createToken(s.mock, "", "",
|
2019-04-25 04:52:08 +07:00
|
|
|
time.Now().Add(1*time.Minute))
|
|
|
|
s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
|
|
|
|
|
2021-12-01 19:11:29 +07:00
|
|
|
s.mock.StorageMock.EXPECT().
|
2021-11-30 13:58:21 +07:00
|
|
|
FindIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String())).
|
2019-04-25 04:52:08 +07:00
|
|
|
Return(true, nil)
|
|
|
|
|
|
|
|
middlewares.IdentityVerificationFinish(newFinishArgs(), next)(s.mock.Ctx)
|
|
|
|
|
|
|
|
s.mock.Assert200KO(s.T(), "Operation failed")
|
|
|
|
assert.Equal(s.T(), "This token has not been generated for this kind of action", s.mock.Hook.LastEntry().Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IdentityVerificationFinishProcess) TestShouldFailForWrongUser() {
|
2021-12-03 07:04:11 +07:00
|
|
|
token, verification := createToken(s.mock, "harry", "EXP_ACTION",
|
2019-04-25 04:52:08 +07:00
|
|
|
time.Now().Add(1*time.Minute))
|
|
|
|
s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
|
|
|
|
|
2021-12-01 19:11:29 +07:00
|
|
|
s.mock.StorageMock.EXPECT().
|
2021-11-30 13:58:21 +07:00
|
|
|
FindIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String())).
|
2019-04-25 04:52:08 +07:00
|
|
|
Return(true, nil)
|
|
|
|
|
|
|
|
args := newFinishArgs()
|
|
|
|
args.IsTokenUserValidFunc = func(ctx *middlewares.AutheliaCtx, username string) bool { return false }
|
|
|
|
middlewares.IdentityVerificationFinish(args, next)(s.mock.Ctx)
|
|
|
|
|
|
|
|
s.mock.Assert200KO(s.T(), "Operation failed")
|
|
|
|
assert.Equal(s.T(), "This token has not been generated for this user", s.mock.Hook.LastEntry().Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IdentityVerificationFinishProcess) TestShouldFailIfTokenCannotBeRemovedFromDB() {
|
2021-12-03 07:04:11 +07:00
|
|
|
token, verification := createToken(s.mock, "john", "EXP_ACTION",
|
2019-04-25 04:52:08 +07:00
|
|
|
time.Now().Add(1*time.Minute))
|
|
|
|
s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
|
|
|
|
|
2021-12-01 19:11:29 +07:00
|
|
|
s.mock.StorageMock.EXPECT().
|
2021-11-30 13:58:21 +07:00
|
|
|
FindIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String())).
|
2019-04-25 04:52:08 +07:00
|
|
|
Return(true, nil)
|
|
|
|
|
2021-12-01 19:11:29 +07:00
|
|
|
s.mock.StorageMock.EXPECT().
|
2022-03-06 12:47:40 +07:00
|
|
|
ConsumeIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String()), gomock.Eq(model.NewNullIP(s.mock.Ctx.RemoteIP()))).
|
2019-04-25 04:52:08 +07:00
|
|
|
Return(fmt.Errorf("cannot remove"))
|
|
|
|
|
|
|
|
middlewares.IdentityVerificationFinish(newFinishArgs(), next)(s.mock.Ctx)
|
|
|
|
|
|
|
|
s.mock.Assert200KO(s.T(), "Operation failed")
|
|
|
|
assert.Equal(s.T(), "cannot remove", s.mock.Hook.LastEntry().Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IdentityVerificationFinishProcess) TestShouldReturn200OnFinishComplete() {
|
2021-12-03 07:04:11 +07:00
|
|
|
token, verification := createToken(s.mock, "john", "EXP_ACTION",
|
2019-04-25 04:52:08 +07:00
|
|
|
time.Now().Add(1*time.Minute))
|
|
|
|
s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
|
|
|
|
|
2021-12-01 19:11:29 +07:00
|
|
|
s.mock.StorageMock.EXPECT().
|
2021-11-30 13:58:21 +07:00
|
|
|
FindIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String())).
|
2019-04-25 04:52:08 +07:00
|
|
|
Return(true, nil)
|
|
|
|
|
2021-12-01 19:11:29 +07:00
|
|
|
s.mock.StorageMock.EXPECT().
|
2022-03-06 12:47:40 +07:00
|
|
|
ConsumeIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String()), gomock.Eq(model.NewNullIP(s.mock.Ctx.RemoteIP()))).
|
2019-04-25 04:52:08 +07:00
|
|
|
Return(nil)
|
|
|
|
|
|
|
|
middlewares.IdentityVerificationFinish(newFinishArgs(), next)(s.mock.Ctx)
|
|
|
|
|
|
|
|
assert.Equal(s.T(), 200, s.mock.Ctx.Response.StatusCode())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRunIdentityVerificationFinish(t *testing.T) {
|
|
|
|
s := new(IdentityVerificationFinishProcess)
|
|
|
|
suite.Run(t, s)
|
|
|
|
}
|