mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
54694c4fca
* [MISC] Ignore errcheck recommendations for legacy code Some of this is likely intended to stay how it is, some could use refactoring, for now we will mark is and ignore it from the linter to be potentially addressed in the future. * [MISC] Ensure files are gofmt-ed
649 lines
22 KiB
Go
649 lines
22 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/authelia/authelia/internal/session"
|
|
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
"github.com/authelia/authelia/internal/authentication"
|
|
"github.com/authelia/authelia/internal/authorization"
|
|
"github.com/authelia/authelia/internal/configuration/schema"
|
|
"github.com/authelia/authelia/internal/mocks"
|
|
)
|
|
|
|
// 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")
|
|
originalURL, err := getOriginalURL(mock.Ctx)
|
|
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()
|
|
mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
|
|
mock.Ctx.Request.Header.Set("X-Forwarded-Host", "home.example.com")
|
|
originalURL, err := getOriginalURL(mock.Ctx)
|
|
assert.NoError(t, err)
|
|
|
|
expectedURL, err := url.ParseRequestURI("https://home.example.com")
|
|
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")
|
|
_, err := getOriginalURL(mock.Ctx)
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "Unable to parse URL extracted from X-Original-URL header: parse htt-ps//home?-.example.com: invalid URI for request", err.Error())
|
|
}
|
|
|
|
func TestShouldRaiseWhenTargetUrlIsMalformed(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
|
|
mock.Ctx.Request.Header.Set("X-Forwarded-Host", "home.example.com")
|
|
mock.Ctx.Request.Header.Set("X-Forwarded-URI", "/abc")
|
|
originalURL, err := getOriginalURL(mock.Ctx)
|
|
assert.NoError(t, err)
|
|
|
|
expectedURL, err := url.ParseRequestURI("https://home.example.com/abc")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedURL, originalURL)
|
|
}
|
|
|
|
func TestShouldRaiseWhenNoHeaderProvidedToDetectTargetURL(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
_, err := getOriginalURL(mock.Ctx)
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "Missing header X-Fowarded-Proto", err.Error())
|
|
}
|
|
|
|
func TestShouldRaiseWhenNoXForwardedHostHeaderProvidedToDetectTargetURL(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
|
|
_, err := getOriginalURL(mock.Ctx)
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "Missing header X-Fowarded-Host", err.Error())
|
|
}
|
|
|
|
func TestShouldRaiseWhenXForwardedProtoIsNotParseable(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "!:;;:,")
|
|
mock.Ctx.Request.Header.Set("X-Forwarded-Host", "myhost.local")
|
|
_, err := getOriginalURL(mock.Ctx)
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "Unable to parse URL !:;;:,://myhost.local: parse !:;;:,://myhost.local: invalid URI for request", err.Error())
|
|
}
|
|
|
|
func TestShouldRaiseWhenXForwardedURIIsNotParseable(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
|
|
mock.Ctx.Request.Header.Set("X-Forwarded-Host", "myhost.local")
|
|
mock.Ctx.Request.Header.Set("X-Forwarded-URI", "!:;;:,")
|
|
_, err := getOriginalURL(mock.Ctx)
|
|
require.Error(t, err)
|
|
assert.Equal(t, "Unable to parse URL https://myhost.local!:;;:,: parse https://myhost.local!:;;:,: invalid port \":,\" after host", err.Error())
|
|
}
|
|
|
|
// Test parseBasicAuth
|
|
func TestShouldRaiseWhenHeaderDoesNotContainBasicPrefix(t *testing.T) {
|
|
_, _, err := parseBasicAuth("alzefzlfzemjfej==")
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "Basic prefix not found in Proxy-Authorization header", err.Error())
|
|
}
|
|
|
|
func TestShouldRaiseWhenCredentialsAreNotInBase64(t *testing.T) {
|
|
_, _, err := parseBasicAuth("Basic alzefzlfzemjfej==")
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "illegal base64 data at input byte 16", err.Error())
|
|
}
|
|
|
|
func TestShouldRaiseWhenCredentialsAreNotInCorrectForm(t *testing.T) {
|
|
// the decoded format should be user:password.
|
|
_, _, err := parseBasicAuth("Basic am9obiBwYXNzd29yZA==")
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "Format of Proxy-Authorization header must be user:password", err.Error())
|
|
}
|
|
|
|
func TestShouldReturnUsernameAndPassword(t *testing.T) {
|
|
// the decoded format should be user:password.
|
|
user, password, err := parseBasicAuth("Basic am9objpwYXNzd29yZA==")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "john", user)
|
|
assert.Equal(t, "password", password)
|
|
}
|
|
|
|
// Test isTargetURLAuthorized
|
|
func TestShouldCheckAuthorizationMatching(t *testing.T) {
|
|
type Rule struct {
|
|
Policy string
|
|
AuthLevel authentication.Level
|
|
ExpectedMatching authorizationMatching
|
|
}
|
|
rules := []Rule{
|
|
{"bypass", authentication.NotAuthenticated, Authorized},
|
|
{"bypass", authentication.OneFactor, Authorized},
|
|
{"bypass", authentication.TwoFactor, Authorized},
|
|
|
|
{"one_factor", authentication.NotAuthenticated, NotAuthorized},
|
|
{"one_factor", authentication.OneFactor, Authorized},
|
|
{"one_factor", authentication.TwoFactor, Authorized},
|
|
|
|
{"two_factor", authentication.NotAuthenticated, NotAuthorized},
|
|
{"two_factor", authentication.OneFactor, NotAuthorized},
|
|
{"two_factor", authentication.TwoFactor, Authorized},
|
|
|
|
{"deny", authentication.NotAuthenticated, NotAuthorized},
|
|
{"deny", authentication.OneFactor, Forbidden},
|
|
{"deny", authentication.TwoFactor, Forbidden},
|
|
}
|
|
|
|
url, _ := url.ParseRequestURI("https://test.example.com")
|
|
|
|
for _, rule := range rules {
|
|
authorizer := authorization.NewAuthorizer(schema.AccessControlConfiguration{
|
|
DefaultPolicy: "deny",
|
|
Rules: []schema.ACLRule{{
|
|
Domains: []string{"test.example.com"},
|
|
Policy: rule.Policy,
|
|
}},
|
|
})
|
|
|
|
username := ""
|
|
if rule.AuthLevel > authentication.NotAuthenticated {
|
|
username = "john"
|
|
}
|
|
|
|
matching := isTargetURLAuthorized(authorizer, *url, username, []string{}, net.ParseIP("127.0.0.1"), rule.AuthLevel)
|
|
assert.Equal(t, rule.ExpectedMatching, matching, "policy=%s, authLevel=%v, expected=%v, actual=%v",
|
|
rule.Policy, rule.AuthLevel, rule.ExpectedMatching, matching)
|
|
}
|
|
}
|
|
|
|
// Test verifyBasicAuth
|
|
func TestShouldVerifyWrongCredentials(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
|
|
Return(false, nil)
|
|
|
|
url, _ := url.ParseRequestURI("https://test.example.com")
|
|
_, _, _, err := verifyBasicAuth([]byte("Basic am9objpwYXNzd29yZA=="), *url, mock.Ctx)
|
|
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
type TestCase struct {
|
|
URL string
|
|
Authorization string
|
|
ExpectedStatusCode int
|
|
}
|
|
|
|
func (tc TestCase) String() string {
|
|
return fmt.Sprintf("url=%s, auth=%s, exp_status=%d", tc.URL, tc.Authorization, tc.ExpectedStatusCode)
|
|
}
|
|
|
|
type BasicAuthorizationSuite struct {
|
|
suite.Suite
|
|
}
|
|
|
|
func NewBasicAuthorizationSuite() *BasicAuthorizationSuite {
|
|
return &BasicAuthorizationSuite{}
|
|
}
|
|
|
|
func (s *BasicAuthorizationSuite) TestShouldNotBeAbleToParseBasicAuth() {
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Request.Header.Set("Proxy-Authorization", "Basic am9objpaaaaaaaaaaaaaaaa")
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://test.example.com")
|
|
|
|
VerifyGet(mock.Ctx)
|
|
|
|
assert.Equal(s.T(), 401, mock.Ctx.Response.StatusCode())
|
|
}
|
|
|
|
func (s *BasicAuthorizationSuite) TestShouldApplyDefaultPolicy() {
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Request.Header.Set("Proxy-Authorization", "Basic am9objpwYXNzd29yZA==")
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://test.example.com")
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
|
|
Return(true, nil)
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
GetDetails(gomock.Eq("john")).
|
|
Return(&authentication.UserDetails{
|
|
Emails: []string{"john@example.com"},
|
|
Groups: []string{"dev", "admins"},
|
|
}, nil)
|
|
|
|
VerifyGet(mock.Ctx)
|
|
|
|
assert.Equal(s.T(), 403, mock.Ctx.Response.StatusCode())
|
|
}
|
|
|
|
func (s *BasicAuthorizationSuite) TestShouldApplyPolicyOfBypassDomain() {
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Request.Header.Set("Proxy-Authorization", "Basic am9objpwYXNzd29yZA==")
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://bypass.example.com")
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
|
|
Return(true, nil)
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
GetDetails(gomock.Eq("john")).
|
|
Return(&authentication.UserDetails{
|
|
Emails: []string{"john@example.com"},
|
|
Groups: []string{"dev", "admins"},
|
|
}, nil)
|
|
|
|
VerifyGet(mock.Ctx)
|
|
|
|
assert.Equal(s.T(), 200, mock.Ctx.Response.StatusCode())
|
|
}
|
|
|
|
func (s *BasicAuthorizationSuite) TestShouldApplyPolicyOfOneFactorDomain() {
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Request.Header.Set("Proxy-Authorization", "Basic am9objpwYXNzd29yZA==")
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://one-factor.example.com")
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
|
|
Return(true, nil)
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
GetDetails(gomock.Eq("john")).
|
|
Return(&authentication.UserDetails{
|
|
Emails: []string{"john@example.com"},
|
|
Groups: []string{"dev", "admins"},
|
|
}, nil)
|
|
|
|
VerifyGet(mock.Ctx)
|
|
|
|
assert.Equal(s.T(), 200, mock.Ctx.Response.StatusCode())
|
|
}
|
|
|
|
func (s *BasicAuthorizationSuite) TestShouldApplyPolicyOfTwoFactorDomain() {
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Request.Header.Set("Proxy-Authorization", "Basic am9objpwYXNzd29yZA==")
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
|
|
Return(true, nil)
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
GetDetails(gomock.Eq("john")).
|
|
Return(&authentication.UserDetails{
|
|
Emails: []string{"john@example.com"},
|
|
Groups: []string{"dev", "admins"},
|
|
}, nil)
|
|
|
|
VerifyGet(mock.Ctx)
|
|
|
|
assert.Equal(s.T(), 401, mock.Ctx.Response.StatusCode())
|
|
}
|
|
|
|
func (s *BasicAuthorizationSuite) TestShouldApplyPolicyOfDenyDomain() {
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Request.Header.Set("Proxy-Authorization", "Basic am9objpwYXNzd29yZA==")
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://deny.example.com")
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
|
|
Return(true, nil)
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
GetDetails(gomock.Eq("john")).
|
|
Return(&authentication.UserDetails{
|
|
Emails: []string{"john@example.com"},
|
|
Groups: []string{"dev", "admins"},
|
|
}, nil)
|
|
|
|
VerifyGet(mock.Ctx)
|
|
|
|
assert.Equal(s.T(), 403, mock.Ctx.Response.StatusCode())
|
|
}
|
|
|
|
func TestShouldVerifyAuthorizationsUsingBasicAuth(t *testing.T) {
|
|
suite.Run(t, NewBasicAuthorizationSuite())
|
|
}
|
|
|
|
func TestShouldVerifyWrongCredentialsInBasicAuth(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
CheckUserPassword(gomock.Eq("john"), gomock.Eq("wrongpass")).
|
|
Return(false, nil)
|
|
|
|
mock.Ctx.Request.Header.Set("Proxy-Authorization", "Basic am9objp3cm9uZ3Bhc3M=")
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://test.example.com")
|
|
|
|
VerifyGet(mock.Ctx)
|
|
expStatus, actualStatus := 401, mock.Ctx.Response.StatusCode()
|
|
assert.Equal(t, expStatus, actualStatus, "URL=%s -> StatusCode=%d != ExpectedStatusCode=%d",
|
|
"https://test.example.com", actualStatus, expStatus)
|
|
}
|
|
|
|
func TestShouldVerifyFailingPasswordCheckingInBasicAuth(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
CheckUserPassword(gomock.Eq("john"), gomock.Eq("wrongpass")).
|
|
Return(false, fmt.Errorf("Failed"))
|
|
|
|
mock.Ctx.Request.Header.Set("Proxy-Authorization", "Basic am9objp3cm9uZ3Bhc3M=")
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://test.example.com")
|
|
|
|
VerifyGet(mock.Ctx)
|
|
expStatus, actualStatus := 401, mock.Ctx.Response.StatusCode()
|
|
assert.Equal(t, expStatus, actualStatus, "URL=%s -> StatusCode=%d != ExpectedStatusCode=%d",
|
|
"https://test.example.com", actualStatus, expStatus)
|
|
}
|
|
|
|
func TestShouldVerifyFailingDetailsFetchingInBasicAuth(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
|
|
Return(true, nil)
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
GetDetails(gomock.Eq("john")).
|
|
Return(nil, fmt.Errorf("Failed"))
|
|
|
|
mock.Ctx.Request.Header.Set("Proxy-Authorization", "Basic am9objpwYXNzd29yZA==")
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://test.example.com")
|
|
|
|
VerifyGet(mock.Ctx)
|
|
expStatus, actualStatus := 401, mock.Ctx.Response.StatusCode()
|
|
assert.Equal(t, expStatus, actualStatus, "URL=%s -> StatusCode=%d != ExpectedStatusCode=%d",
|
|
"https://test.example.com", actualStatus, expStatus)
|
|
}
|
|
|
|
type Pair struct {
|
|
URL string
|
|
Username string
|
|
AuthenticationLevel authentication.Level
|
|
ExpectedStatusCode int
|
|
}
|
|
|
|
func (p Pair) String() string {
|
|
return fmt.Sprintf("url=%s, username=%s, auth_lvl=%d, exp_status=%d",
|
|
p.URL, p.Username, p.AuthenticationLevel, p.ExpectedStatusCode)
|
|
}
|
|
|
|
func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) {
|
|
testCases := []Pair{
|
|
{"https://test.example.com", "", authentication.NotAuthenticated, 401},
|
|
{"https://bypass.example.com", "", authentication.NotAuthenticated, 200},
|
|
{"https://one-factor.example.com", "", authentication.NotAuthenticated, 401},
|
|
{"https://two-factor.example.com", "", authentication.NotAuthenticated, 401},
|
|
{"https://deny.example.com", "", authentication.NotAuthenticated, 401},
|
|
|
|
{"https://test.example.com", "john", authentication.OneFactor, 403},
|
|
{"https://bypass.example.com", "john", authentication.OneFactor, 200},
|
|
{"https://one-factor.example.com", "john", authentication.OneFactor, 200},
|
|
{"https://two-factor.example.com", "john", authentication.OneFactor, 401},
|
|
{"https://deny.example.com", "john", authentication.OneFactor, 403},
|
|
|
|
{"https://test.example.com", "john", authentication.TwoFactor, 403},
|
|
{"https://bypass.example.com", "john", authentication.TwoFactor, 200},
|
|
{"https://one-factor.example.com", "john", authentication.TwoFactor, 200},
|
|
{"https://two-factor.example.com", "john", authentication.TwoFactor, 200},
|
|
{"https://deny.example.com", "john", authentication.TwoFactor, 403},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
testCase := testCase
|
|
t.Run(testCase.String(), func(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
|
|
userSession := mock.Ctx.GetSession()
|
|
userSession.Username = testCase.Username
|
|
userSession.AuthenticationLevel = testCase.AuthenticationLevel
|
|
mock.Ctx.SaveSession(userSession) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", testCase.URL)
|
|
|
|
VerifyGet(mock.Ctx)
|
|
expStatus, actualStatus := testCase.ExpectedStatusCode, mock.Ctx.Response.StatusCode()
|
|
assert.Equal(t, expStatus, actualStatus, "URL=%s -> AuthLevel=%d, StatusCode=%d != ExpectedStatusCode=%d",
|
|
testCase.URL, testCase.AuthenticationLevel, actualStatus, expStatus)
|
|
|
|
if testCase.ExpectedStatusCode == 200 && testCase.Username != "" {
|
|
assert.Equal(t, []byte(testCase.Username), mock.Ctx.Response.Header.Peek("Remote-User"))
|
|
} else {
|
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek("Remote-User"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestShouldDestroySessionWhenInactiveForTooLong(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
|
|
clock := mocks.TestingClock{}
|
|
clock.Set(time.Now())
|
|
|
|
mock.Ctx.Configuration.Session.Inactivity = "10"
|
|
// Reload the session provider since the configuration is indirect
|
|
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session)
|
|
assert.Equal(t, time.Second*10, mock.Ctx.Providers.SessionProvider.Inactivity)
|
|
|
|
userSession := mock.Ctx.GetSession()
|
|
userSession.Username = "john"
|
|
userSession.AuthenticationLevel = authentication.TwoFactor
|
|
userSession.LastActivity = clock.Now().Add(-1 * time.Hour).Unix()
|
|
mock.Ctx.SaveSession(userSession) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
|
|
|
|
VerifyGet(mock.Ctx)
|
|
|
|
// The session has been destroyed
|
|
newUserSession := mock.Ctx.GetSession()
|
|
assert.Equal(t, "", newUserSession.Username)
|
|
assert.Equal(t, authentication.NotAuthenticated, newUserSession.AuthenticationLevel)
|
|
}
|
|
|
|
func TestShouldDestroySessionWhenInactiveForTooLongUsingDurationNotation(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
|
|
clock := mocks.TestingClock{}
|
|
clock.Set(time.Now())
|
|
|
|
mock.Ctx.Configuration.Session.Inactivity = "10s"
|
|
// Reload the session provider since the configuration is indirect
|
|
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session)
|
|
assert.Equal(t, time.Second*10, mock.Ctx.Providers.SessionProvider.Inactivity)
|
|
|
|
userSession := mock.Ctx.GetSession()
|
|
userSession.Username = "john"
|
|
userSession.AuthenticationLevel = authentication.TwoFactor
|
|
userSession.LastActivity = clock.Now().Add(-1 * time.Hour).Unix()
|
|
mock.Ctx.SaveSession(userSession) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
|
|
|
|
VerifyGet(mock.Ctx)
|
|
|
|
// The session has been destroyed
|
|
newUserSession := mock.Ctx.GetSession()
|
|
assert.Equal(t, "", newUserSession.Username)
|
|
assert.Equal(t, authentication.NotAuthenticated, newUserSession.AuthenticationLevel)
|
|
}
|
|
|
|
func TestShouldKeepSessionWhenUserCheckedRememberMeAndIsInactiveForTooLong(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
|
|
clock := mocks.TestingClock{}
|
|
clock.Set(time.Now())
|
|
|
|
mock.Ctx.Configuration.Session.Inactivity = "10"
|
|
|
|
userSession := mock.Ctx.GetSession()
|
|
userSession.Username = "john"
|
|
userSession.AuthenticationLevel = authentication.TwoFactor
|
|
userSession.LastActivity = clock.Now().Add(-1 * time.Hour).Unix()
|
|
userSession.KeepMeLoggedIn = true
|
|
mock.Ctx.SaveSession(userSession) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
|
|
|
|
VerifyGet(mock.Ctx)
|
|
|
|
// The session has been destroyed
|
|
newUserSession := mock.Ctx.GetSession()
|
|
assert.Equal(t, "john", newUserSession.Username)
|
|
assert.Equal(t, authentication.TwoFactor, newUserSession.AuthenticationLevel)
|
|
}
|
|
|
|
func TestShouldKeepSessionWhenInactivityTimeoutHasNotBeenExceeded(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
|
|
clock := mocks.TestingClock{}
|
|
clock.Set(time.Now())
|
|
|
|
mock.Ctx.Configuration.Session.Inactivity = "10"
|
|
|
|
userSession := mock.Ctx.GetSession()
|
|
userSession.Username = "john"
|
|
userSession.AuthenticationLevel = authentication.TwoFactor
|
|
userSession.LastActivity = clock.Now().Add(-1 * time.Second).Unix()
|
|
mock.Ctx.SaveSession(userSession) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
|
|
|
|
VerifyGet(mock.Ctx)
|
|
|
|
// The session has been destroyed
|
|
newUserSession := mock.Ctx.GetSession()
|
|
assert.Equal(t, "john", newUserSession.Username)
|
|
assert.Equal(t, authentication.TwoFactor, newUserSession.AuthenticationLevel)
|
|
}
|
|
|
|
func TestShouldURLEncodeRedirectionURLParameter(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
defer mock.Close()
|
|
|
|
userSession := mock.Ctx.GetSession()
|
|
userSession.Username = "john"
|
|
userSession.AuthenticationLevel = authentication.NotAuthenticated
|
|
mock.Ctx.SaveSession(userSession) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
|
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
|
|
mock.Ctx.Request.SetHost("mydomain.com")
|
|
mock.Ctx.Request.SetRequestURI("/?rd=https://auth.mydomain.com")
|
|
|
|
VerifyGet(mock.Ctx)
|
|
|
|
assert.Equal(t, "Found. Redirecting to https://auth.mydomain.com?rd=https%3A%2F%2Ftwo-factor.example.com",
|
|
string(mock.Ctx.Response.Body()))
|
|
}
|
|
|
|
func TestIsDomainProtected(t *testing.T) {
|
|
GetURL := func(u string) *url.URL {
|
|
x, err := url.ParseRequestURI(u)
|
|
require.NoError(t, err)
|
|
return x
|
|
}
|
|
|
|
assert.True(t, isURLUnderProtectedDomain(
|
|
GetURL("http://mytest.example.com/abc/?query=abc"), "example.com"))
|
|
|
|
assert.True(t, isURLUnderProtectedDomain(
|
|
GetURL("http://example.com/abc/?query=abc"), "example.com"))
|
|
|
|
assert.True(t, isURLUnderProtectedDomain(
|
|
GetURL("https://mytest.example.com/abc/?query=abc"), "example.com"))
|
|
|
|
// cookies readable by a service on a machine is also readable by a service on the same machine
|
|
// with a different port as mentioned in https://tools.ietf.org/html/rfc6265#section-8.5
|
|
assert.True(t, isURLUnderProtectedDomain(
|
|
GetURL("https://mytest.example.com:8080/abc/?query=abc"), "example.com"))
|
|
}
|
|
|
|
func TestSchemeIsHTTPS(t *testing.T) {
|
|
GetURL := func(u string) *url.URL {
|
|
x, err := url.ParseRequestURI(u)
|
|
require.NoError(t, err)
|
|
return x
|
|
}
|
|
|
|
assert.False(t, isSchemeHTTPS(
|
|
GetURL("http://mytest.example.com/abc/?query=abc")))
|
|
assert.False(t, isSchemeHTTPS(
|
|
GetURL("ws://mytest.example.com/abc/?query=abc")))
|
|
assert.False(t, isSchemeHTTPS(
|
|
GetURL("wss://mytest.example.com/abc/?query=abc")))
|
|
assert.True(t, isSchemeHTTPS(
|
|
GetURL("https://mytest.example.com/abc/?query=abc")))
|
|
}
|
|
|
|
func TestSchemeIsWSS(t *testing.T) {
|
|
GetURL := func(u string) *url.URL {
|
|
x, err := url.ParseRequestURI(u)
|
|
require.NoError(t, err)
|
|
return x
|
|
}
|
|
|
|
assert.False(t, isSchemeWSS(
|
|
GetURL("ws://mytest.example.com/abc/?query=abc")))
|
|
assert.False(t, isSchemeWSS(
|
|
GetURL("http://mytest.example.com/abc/?query=abc")))
|
|
assert.False(t, isSchemeWSS(
|
|
GetURL("https://mytest.example.com/abc/?query=abc")))
|
|
assert.True(t, isSchemeWSS(
|
|
GetURL("wss://mytest.example.com/abc/?query=abc")))
|
|
}
|