2019-04-24 21:52:08 +00:00
|
|
|
package middlewares_test
|
|
|
|
|
|
|
|
import (
|
2023-04-30 00:52:45 +00:00
|
|
|
"net"
|
2021-05-04 22:06:05 +00:00
|
|
|
"net/url"
|
2019-04-24 21:52:08 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/golang/mock/gomock"
|
|
|
|
"github.com/stretchr/testify/assert"
|
2022-10-20 02:16:36 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2019-04-24 21:52:08 +00:00
|
|
|
"github.com/valyala/fasthttp"
|
2020-04-05 12:37:21 +00:00
|
|
|
|
2021-08-11 01:04:35 +00:00
|
|
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
|
|
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
|
|
|
"github.com/authelia/authelia/v4/internal/mocks"
|
2022-03-28 01:26:30 +00:00
|
|
|
"github.com/authelia/authelia/v4/internal/model"
|
2023-01-07 00:19:41 +00:00
|
|
|
"github.com/authelia/authelia/v4/internal/random"
|
2021-08-11 01:04:35 +00:00
|
|
|
"github.com/authelia/authelia/v4/internal/session"
|
2019-04-24 21:52:08 +00:00
|
|
|
)
|
|
|
|
|
2023-04-30 00:52:45 +00:00
|
|
|
func TestAutheliaCtx_RemoteIP(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
have []byte
|
|
|
|
expected net.IP
|
|
|
|
}{
|
|
|
|
{"ShouldDefaultToRemoteAddr", nil, net.ParseIP("127.0.0.127")},
|
|
|
|
{"ShouldParseProperlyFormattedXFFWithIPv4", []byte("192.168.1.1, 127.0.0.1"), net.ParseIP("192.168.1.1")},
|
|
|
|
{"ShouldParseProperlyFormattedXFFWithIPv6", []byte("2001:db8:85a3:8d3:1319:8a2e:370:7348, 127.0.0.1"), net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348")},
|
|
|
|
{"ShouldFallbackToRemoteAddrOnImproperlyFormattedXFFWithIPv6", []byte("[2001:db8:85a3:8d3:1319:8a2e:370:7348], 127.0.0.1"), net.ParseIP("127.0.0.127")},
|
|
|
|
{"ShouldFallbackToRemoteAddrOnBlankXFFHeader", []byte(""), net.ParseIP("127.0.0.127")},
|
|
|
|
{"ShouldFallbackToRemoteAddrOnBlankXFFEntry", []byte(", 127.0.0.1"), net.ParseIP("127.0.0.127")},
|
|
|
|
{"ShouldFallbackToRemoteAddrOnBadXFFEntry", []byte("abc, 127.0.0.1"), net.ParseIP("127.0.0.127")},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
|
|
|
|
|
|
|
mock.Ctx.SetRemoteAddr(&net.TCPAddr{Port: 80, IP: net.ParseIP("127.0.0.127")})
|
|
|
|
|
|
|
|
if tc.have != nil {
|
|
|
|
mock.Ctx.RequestCtx.Request.Header.SetBytesV(fasthttp.HeaderXForwardedFor, tc.have)
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Equal(t, tc.expected, mock.Ctx.RemoteIP())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-03 03:49:02 +00:00
|
|
|
func TestContentTypes(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
setup func(ctx *middlewares.AutheliaCtx) (err error)
|
|
|
|
expected string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "ApplicationJSON",
|
|
|
|
setup: func(ctx *middlewares.AutheliaCtx) (err error) {
|
|
|
|
ctx.SetContentTypeApplicationJSON()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
expected: "application/json; charset=utf-8",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ApplicationYAML",
|
|
|
|
setup: func(ctx *middlewares.AutheliaCtx) (err error) {
|
|
|
|
ctx.SetContentTypeApplicationYAML()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
expected: "application/yaml; charset=utf-8",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "TextPlain",
|
|
|
|
setup: func(ctx *middlewares.AutheliaCtx) (err error) {
|
|
|
|
ctx.SetContentTypeTextPlain()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
expected: "text/plain; charset=utf-8",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "TextHTML",
|
|
|
|
setup: func(ctx *middlewares.AutheliaCtx) (err error) {
|
|
|
|
ctx.SetContentTypeTextHTML()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
expected: "text/html; charset=utf-8",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
|
|
|
|
|
|
|
assert.NoError(t, tc.setup(mock.Ctx))
|
|
|
|
|
|
|
|
assert.Equal(t, tc.expected, string(mock.Ctx.Response.Header.ContentType()))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-20 02:16:36 +00:00
|
|
|
func TestIssuerURL(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
proto, host, base string
|
|
|
|
expected string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Standard",
|
|
|
|
proto: "https", host: "auth.example.com", base: "",
|
|
|
|
expected: "https://auth.example.com",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Base",
|
|
|
|
proto: "https", host: "example.com", base: "auth",
|
|
|
|
expected: "https://example.com/auth",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "NoHost",
|
|
|
|
proto: "https", host: "", base: "",
|
2022-12-17 00:49:05 +00:00
|
|
|
expected: "https:",
|
2022-10-20 02:16:36 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
|
|
|
|
2023-01-03 03:49:02 +00:00
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, tc.proto)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedHost, tc.host)
|
2022-10-20 02:16:36 +00:00
|
|
|
|
|
|
|
if tc.base != "" {
|
|
|
|
mock.Ctx.SetUserValue("base_url", tc.base)
|
|
|
|
}
|
|
|
|
|
2022-12-17 00:49:05 +00:00
|
|
|
actual := mock.Ctx.RootURL()
|
2022-10-20 02:16:36 +00:00
|
|
|
|
2022-12-17 00:49:05 +00:00
|
|
|
require.NotNil(t, actual)
|
2022-10-20 02:16:36 +00:00
|
|
|
|
2022-12-17 00:49:05 +00:00
|
|
|
assert.Equal(t, tc.expected, actual.String())
|
|
|
|
assert.Equal(t, tc.proto, actual.Scheme)
|
|
|
|
assert.Equal(t, tc.host, actual.Host)
|
|
|
|
assert.Equal(t, tc.base, actual.Path)
|
2022-10-20 02:16:36 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-24 21:52:08 +00:00
|
|
|
func TestShouldCallNextWithAutheliaCtx(t *testing.T) {
|
|
|
|
ctrl := gomock.NewController(t)
|
2023-05-25 01:17:35 +00:00
|
|
|
defer ctrl.Finish()
|
|
|
|
|
2019-04-24 21:52:08 +00:00
|
|
|
ctx := &fasthttp.RequestCtx{}
|
|
|
|
configuration := schema.Configuration{}
|
|
|
|
userProvider := mocks.NewMockUserProvider(ctrl)
|
feat(session): add redis sentinel provider (#1768)
* feat(session): add redis sentinel provider
* refactor(session): use int for ports as per go standards
* refactor(configuration): adjust tests and validation
* refactor(configuration): add err format consts
* refactor(configuration): explicitly map redis structs
* refactor(session): merge redis/redis sentinel providers
* refactor(session): add additional checks to redis providers
* feat(session): add redis cluster provider
* fix: update config for new values
* fix: provide nil certpool to affected tests/mocks
* test: add additional tests to cover uncovered code
* docs: expand explanation of host and nodes relation for redis
* ci: add redis-sentinel to suite highavailability, add redis-sentinel quorum
* fix(session): sentinel password
* test: use redis alpine library image for redis sentinel, use expose instead of ports, use redis ip, adjust redis ip range, adjust redis config
* test: make entrypoint.sh executable, fix entrypoint.sh if/elif
* test: add redis failover tests
* test: defer docker start, adjust sleep, attempt logout before login, attempt visit before login and tune timeouts, add additional logging
* test: add sentinel integration test
* test: add secondary node failure to tests, fix password usage, bump test timeout, add sleep
* feat: use sentinel failover cluster
* fix: renamed addrs to sentineladdrs upstream
* test(session): sentinel failover
* test: add redis standard back into testing
* test: move redis standalone test to traefik2
* fix/docs: apply suggestions from code review
2021-03-09 23:03:05 +00:00
|
|
|
sessionProvider := session.NewProvider(configuration.Session, nil)
|
2019-04-24 21:52:08 +00:00
|
|
|
providers := middlewares.Providers{
|
|
|
|
UserProvider: userProvider,
|
|
|
|
SessionProvider: sessionProvider,
|
2023-01-07 00:19:41 +00:00
|
|
|
Random: random.NewMathematical(),
|
2019-04-24 21:52:08 +00:00
|
|
|
}
|
|
|
|
nextCalled := false
|
|
|
|
|
2022-06-10 01:34:43 +00:00
|
|
|
middleware := middlewares.NewBridgeBuilder(configuration, providers).Build()
|
|
|
|
|
|
|
|
middleware(func(actx *middlewares.AutheliaCtx) {
|
2019-04-24 21:52:08 +00:00
|
|
|
// Authelia context wraps the request.
|
|
|
|
assert.Equal(t, ctx, actx.RequestCtx)
|
|
|
|
nextCalled = true
|
|
|
|
})(ctx)
|
|
|
|
|
|
|
|
assert.True(t, nextCalled)
|
|
|
|
}
|
2021-05-04 22:06:05 +00:00
|
|
|
|
|
|
|
// Test getOriginalURL.
|
|
|
|
func TestShouldGetOriginalURLFromOriginalURLHeader(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
|
|
|
|
|
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://home.example.com")
|
2023-01-25 09:36:40 +00:00
|
|
|
originalURL, err := mock.Ctx.GetXOriginalURLOrXForwardedURL()
|
2021-05-04 22:06:05 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
expectedURL, err := url.ParseRequestURI("https://home.example.com")
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, expectedURL, originalURL)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestShouldGetOriginalURLFromForwardedHeadersWithoutURI(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
2023-01-03 03:49:02 +00:00
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https")
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedHost, "home.example.com")
|
2023-01-25 09:36:40 +00:00
|
|
|
originalURL, err := mock.Ctx.GetXOriginalURLOrXForwardedURL()
|
2021-05-04 22:06:05 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2022-02-06 13:37:28 +00:00
|
|
|
expectedURL, err := url.ParseRequestURI("https://home.example.com/")
|
2021-05-04 22:06:05 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, expectedURL, originalURL)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestShouldGetOriginalURLFromForwardedHeadersWithURI(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "htt-ps//home?-.example.com")
|
2023-01-25 09:36:40 +00:00
|
|
|
_, err := mock.Ctx.GetXOriginalURLOrXForwardedURL()
|
2021-05-04 22:06:05 +00:00
|
|
|
assert.Error(t, err)
|
2023-01-25 09:36:40 +00:00
|
|
|
assert.EqualError(t, err, "failed to parse X-Original-URL header: parse \"htt-ps//home?-.example.com\": invalid URI for request")
|
2021-05-04 22:06:05 +00:00
|
|
|
}
|
2022-02-06 13:37:28 +00:00
|
|
|
|
|
|
|
func TestShouldFallbackToNonXForwardedHeaders(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
2023-04-15 05:03:14 +00:00
|
|
|
mock.Ctx.Request.Header.Del(fasthttp.HeaderXForwardedHost)
|
2023-01-12 10:57:44 +00:00
|
|
|
|
2022-02-06 13:37:28 +00:00
|
|
|
defer mock.Close()
|
|
|
|
|
|
|
|
mock.Ctx.RequestCtx.Request.SetRequestURI("/2fa/one-time-password")
|
|
|
|
mock.Ctx.RequestCtx.Request.SetHost("auth.example.com:1234")
|
|
|
|
|
|
|
|
assert.Equal(t, []byte("http"), mock.Ctx.XForwardedProto())
|
2023-01-25 09:36:40 +00:00
|
|
|
assert.Equal(t, []byte("auth.example.com:1234"), mock.Ctx.GetXForwardedHost())
|
|
|
|
assert.Equal(t, []byte("/2fa/one-time-password"), mock.Ctx.GetXForwardedURI())
|
2022-02-06 13:37:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestShouldOnlyFallbackToNonXForwardedHeadersWhenNil(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
|
|
|
|
2023-04-15 05:03:14 +00:00
|
|
|
mock.Ctx.Request.Header.Del(fasthttp.HeaderXForwardedHost)
|
2023-01-12 10:57:44 +00:00
|
|
|
|
2022-02-06 13:37:28 +00:00
|
|
|
mock.Ctx.RequestCtx.Request.SetRequestURI("/2fa/one-time-password")
|
|
|
|
mock.Ctx.RequestCtx.Request.SetHost("localhost")
|
|
|
|
mock.Ctx.RequestCtx.Request.Header.Set(fasthttp.HeaderXForwardedHost, "auth.example.com:1234")
|
|
|
|
mock.Ctx.RequestCtx.Request.Header.Set("X-Forwarded-URI", "/base/2fa/one-time-password")
|
2023-01-03 03:49:02 +00:00
|
|
|
mock.Ctx.RequestCtx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https")
|
2023-04-15 05:03:14 +00:00
|
|
|
mock.Ctx.RequestCtx.Request.Header.Set("X-Forwarded-Method", fasthttp.MethodGet)
|
2022-02-06 13:37:28 +00:00
|
|
|
|
|
|
|
assert.Equal(t, []byte("https"), mock.Ctx.XForwardedProto())
|
2023-01-25 09:36:40 +00:00
|
|
|
assert.Equal(t, []byte("auth.example.com:1234"), mock.Ctx.GetXForwardedHost())
|
|
|
|
assert.Equal(t, []byte("/base/2fa/one-time-password"), mock.Ctx.GetXForwardedURI())
|
2023-04-15 05:03:14 +00:00
|
|
|
assert.Equal(t, []byte(fasthttp.MethodGet), mock.Ctx.XForwardedMethod())
|
2022-02-06 13:37:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestShouldDetectXHR(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
|
|
|
|
|
|
|
mock.Ctx.RequestCtx.Request.Header.Set(fasthttp.HeaderXRequestedWith, "XMLHttpRequest")
|
|
|
|
|
|
|
|
assert.True(t, mock.Ctx.IsXHR())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestShouldDetectNonXHR(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
|
|
|
|
|
|
|
assert.False(t, mock.Ctx.IsXHR())
|
|
|
|
}
|
2022-03-28 01:26:30 +00:00
|
|
|
|
|
|
|
func TestShouldReturnCorrectSecondFactorMethods(t *testing.T) {
|
|
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
|
|
|
|
2022-04-15 23:34:26 +00:00
|
|
|
mock.Ctx.Configuration.DuoAPI.Disable = true
|
|
|
|
|
2023-04-10 07:01:23 +00:00
|
|
|
assert.Equal(t, []string{model.SecondFactorMethodTOTP, model.SecondFactorMethodWebAuthn}, mock.Ctx.AvailableSecondFactorMethods())
|
2022-03-28 01:26:30 +00:00
|
|
|
|
2022-04-15 23:34:26 +00:00
|
|
|
mock.Ctx.Configuration.DuoAPI.Disable = false
|
2022-03-28 01:26:30 +00:00
|
|
|
|
2023-04-10 07:01:23 +00:00
|
|
|
assert.Equal(t, []string{model.SecondFactorMethodTOTP, model.SecondFactorMethodWebAuthn, model.SecondFactorMethodDuo}, mock.Ctx.AvailableSecondFactorMethods())
|
2022-03-28 01:26:30 +00:00
|
|
|
|
|
|
|
mock.Ctx.Configuration.TOTP.Disable = true
|
|
|
|
|
2023-04-10 07:01:23 +00:00
|
|
|
assert.Equal(t, []string{model.SecondFactorMethodWebAuthn, model.SecondFactorMethodDuo}, mock.Ctx.AvailableSecondFactorMethods())
|
2022-03-28 01:26:30 +00:00
|
|
|
|
2023-04-14 16:04:42 +00:00
|
|
|
mock.Ctx.Configuration.WebAuthn.Disable = true
|
2022-03-28 01:26:30 +00:00
|
|
|
|
|
|
|
assert.Equal(t, []string{model.SecondFactorMethodDuo}, mock.Ctx.AvailableSecondFactorMethods())
|
|
|
|
|
2022-04-15 23:34:26 +00:00
|
|
|
mock.Ctx.Configuration.DuoAPI.Disable = true
|
2022-03-28 01:26:30 +00:00
|
|
|
|
|
|
|
assert.Equal(t, []string{}, mock.Ctx.AvailableSecondFactorMethods())
|
|
|
|
}
|