mirror of https://github.com/authelia/authelia.git
1646 lines
46 KiB
Go
1646 lines
46 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/suite"
|
|
"github.com/valyala/fasthttp"
|
|
"go.uber.org/mock/gomock"
|
|
|
|
"github.com/authelia/authelia/v4/internal/authentication"
|
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
|
"github.com/authelia/authelia/v4/internal/mocks"
|
|
"github.com/authelia/authelia/v4/internal/model"
|
|
"github.com/authelia/authelia/v4/internal/session"
|
|
"github.com/authelia/authelia/v4/internal/utils"
|
|
)
|
|
|
|
type AuthzSuite struct {
|
|
suite.Suite
|
|
|
|
implementation AuthzImplementation
|
|
builder *AuthzBuilder
|
|
setRequest func(ctx *middlewares.AutheliaCtx, method string, targetURI *url.URL, accept bool, xhr bool)
|
|
}
|
|
|
|
func (s *AuthzSuite) GetMock(config *schema.Configuration, targetURI *url.URL, session *session.UserSession) *mocks.MockAutheliaCtx {
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
if session != nil {
|
|
domain := mock.Ctx.GetCookieDomainFromTargetURI(targetURI)
|
|
|
|
provider, err := mock.Ctx.GetCookieDomainSessionProvider(domain)
|
|
s.Require().NoError(err)
|
|
|
|
s.Require().NoError(provider.SaveSession(mock.Ctx.RequestCtx, *session))
|
|
}
|
|
|
|
return mock
|
|
}
|
|
|
|
func (s *AuthzSuite) RequireParseRequestURI(rawURL string) *url.URL {
|
|
u, err := url.ParseRequestURI(rawURL)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
return u
|
|
}
|
|
|
|
func (s *AuthzSuite) ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock *mocks.MockAutheliaCtx) {
|
|
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
|
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
}
|
|
|
|
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
}
|
|
|
|
func (s *AuthzSuite) Builder() (builder *AuthzBuilder) {
|
|
if s.builder != nil {
|
|
return s.builder
|
|
}
|
|
|
|
switch s.implementation {
|
|
case AuthzImplExtAuthz:
|
|
return NewAuthzBuilder().WithImplementationExtAuthz()
|
|
case AuthzImplForwardAuth:
|
|
return NewAuthzBuilder().WithImplementationForwardAuth()
|
|
case AuthzImplAuthRequest:
|
|
return NewAuthzBuilder().WithImplementationAuthRequest()
|
|
case AuthzImplLegacy:
|
|
return NewAuthzBuilder().WithImplementationLegacy()
|
|
}
|
|
|
|
s.T().FailNow()
|
|
|
|
return
|
|
}
|
|
|
|
func (s *AuthzSuite) BuilderWithBearerScheme() (builder *AuthzBuilder) {
|
|
switch s.implementation {
|
|
case AuthzImplExtAuthz:
|
|
return NewAuthzBuilder().WithImplementationExtAuthz().WithStrategies(NewHeaderProxyAuthorizationAuthnStrategy(model.AuthorizationSchemeBasic.String(), model.AuthorizationSchemeBearer.String()), NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDurationAlways()))
|
|
case AuthzImplForwardAuth:
|
|
return NewAuthzBuilder().WithImplementationForwardAuth().WithStrategies(NewHeaderProxyAuthorizationAuthnStrategy(model.AuthorizationSchemeBasic.String(), model.AuthorizationSchemeBearer.String()), NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDurationAlways()))
|
|
case AuthzImplAuthRequest:
|
|
return NewAuthzBuilder().WithImplementationAuthRequest().WithStrategies(NewHeaderProxyAuthorizationAuthRequestAuthnStrategy(model.AuthorizationSchemeBasic.String(), model.AuthorizationSchemeBearer.String()), NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDurationAlways()))
|
|
case AuthzImplLegacy:
|
|
return NewAuthzBuilder().WithImplementationLegacy().WithStrategies(NewHeaderLegacyAuthnStrategy(), NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDurationAlways()))
|
|
default:
|
|
s.T().FailNow()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldNotBeAbleToParseBasicAuth() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
authz := s.Builder().Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://test.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Basic am9objpaaaaaaaaaaaaaaaa")
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
switch s.implementation {
|
|
case AuthzImplAuthRequest, AuthzImplLegacy:
|
|
s.Equal(fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
|
s.Equal(`Basic realm="Authorization Required"`, string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate)))
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate))
|
|
default:
|
|
s.Equal(fasthttp.StatusProxyAuthRequired, mock.Ctx.Response.StatusCode())
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate))
|
|
s.Equal(`Basic realm="Authorization Required"`, string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate)))
|
|
}
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldApplyDefaultPolicy() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
authz := s.Builder().Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://test.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Basic am9objpwYXNzd29yZA==")
|
|
|
|
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)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusForbidden, mock.Ctx.Response.StatusCode())
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate))
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate))
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldDenyObject() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
value string
|
|
}{
|
|
{
|
|
"NotProtected",
|
|
"https://test.not-a-protected-domain.com",
|
|
},
|
|
{
|
|
"Insecure",
|
|
"http://test.example.com",
|
|
},
|
|
}
|
|
|
|
authz := s.Builder().Build()
|
|
|
|
for _, tc := range testCases {
|
|
s.T().Run(tc.name, func(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(t)
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI(tc.value)
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
switch s.implementation {
|
|
case AuthzImplLegacy:
|
|
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
|
default:
|
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldApplyPolicyOfBypassDomain() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
authz := s.Builder().Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Basic am9objpwYXNzd29yZA==")
|
|
|
|
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)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate))
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate))
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldVerifyFailureToGetDetailsUsingBasicScheme() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
authz := s.Builder().Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Basic am9objpwYXNzd29yZA==")
|
|
|
|
gomock.InOrder(
|
|
mock.UserProviderMock.EXPECT().
|
|
CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
|
|
Return(true, nil),
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
GetDetails(gomock.Eq("john")).
|
|
Return(nil, fmt.Errorf("generic failure")),
|
|
)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
switch s.implementation {
|
|
case AuthzImplAuthRequest, AuthzImplLegacy:
|
|
s.Equal(fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
|
s.Equal(`Basic realm="Authorization Required"`, string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate)))
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate))
|
|
default:
|
|
s.Equal(fasthttp.StatusProxyAuthRequired, mock.Ctx.Response.StatusCode())
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate))
|
|
s.Equal(`Basic realm="Authorization Required"`, string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate)))
|
|
}
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldVerifyBypassWithErrorToGetDetailsUsingBasicScheme() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
authz := s.Builder().Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Basic am9objpwYXNzd29yZA==")
|
|
|
|
gomock.InOrder(
|
|
mock.UserProviderMock.EXPECT().
|
|
CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
|
|
Return(true, nil),
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
GetDetails(gomock.Eq("john")).
|
|
Return(nil, fmt.Errorf("generic failure")),
|
|
)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldVerifyBypassWithErrorToGetDetailsUsingBearerScheme() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
authz := s.Builder().Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Bearer am9objpwYXNzd29yZA==")
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldVerifyBypassWithErrorToGetDetailsUsingBearerSchemePossibleToken() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
authz := s.Builder().Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Bearer authelia_at_aaaa.aaaaaa")
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldVerifyOneFactorWithErrorToGetDetailsUsingBearerScheme() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
authz := s.BuilderWithBearerScheme().Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Bearer am9objpwYXNzd29yZA==")
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
switch s.implementation {
|
|
case AuthzImplExtAuthz, AuthzImplForwardAuth:
|
|
s.Equal(fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
|
default:
|
|
s.Equal(fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
|
}
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldVerifyOneFactorWithErrorToGetDetailsUsingBearerSchemePossibleToken() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
authz := s.BuilderWithBearerScheme().Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Bearer authelia_at_aaaa.aaaaaa")
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
switch s.implementation {
|
|
case AuthzImplExtAuthz, AuthzImplForwardAuth:
|
|
s.Equal(fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
|
default:
|
|
s.Equal(fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
|
}
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldNotFailOnMissingEmail() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Clock = &mock.Clock
|
|
|
|
mock.Clock.Set(time.Now())
|
|
|
|
authz := s.Builder().WithConfig(&mock.Ctx.Configuration).Build()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
userSession, err := mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
userSession.Username = testUsername
|
|
userSession.DisplayName = "John Smith"
|
|
userSession.Groups = []string{"abc,123"}
|
|
userSession.Emails = nil
|
|
userSession.AuthenticationLevel = authentication.OneFactor
|
|
userSession.RefreshTTL = mock.Clock.Now().Add(5 * time.Minute)
|
|
|
|
s.Require().NoError(mock.Ctx.SaveSession(userSession))
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
s.Equal(testUsername, string(mock.Ctx.Response.Header.PeekBytes(headerRemoteUser)))
|
|
s.Equal("John Smith", string(mock.Ctx.Response.Header.PeekBytes(headerRemoteName)))
|
|
s.Equal("abc,123", string(mock.Ctx.Response.Header.PeekBytes(headerRemoteGroups)))
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldApplyPolicyOfOneFactorDomain() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
authz := s.Builder().Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Basic am9objpwYXNzd29yZA==")
|
|
|
|
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)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate))
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate))
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldHandleAnyCaseSchemeParameter() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
testCases := []struct {
|
|
name, scheme string
|
|
}{
|
|
{"Standard", "Basic"},
|
|
{"LowerCase", "basic"},
|
|
{"UpperCase", "BASIC"},
|
|
{"MixedCase", "BaSIc"},
|
|
}
|
|
|
|
authz := s.Builder().Build()
|
|
|
|
for _, tc := range testCases {
|
|
s.T().Run(tc.name, func(t *testing.T) {
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, fmt.Sprintf("%s am9objpwYXNzd29yZA==", tc.scheme))
|
|
|
|
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)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate))
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate))
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldApplyPolicyOfTwoFactorDomain() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
authz := s.Builder().Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Basic am9objpwYXNzd29yZA==")
|
|
|
|
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)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
switch s.implementation {
|
|
case AuthzImplAuthRequest, AuthzImplLegacy:
|
|
s.Equal(fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
|
s.Equal(`Basic realm="Authorization Required"`, string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate)))
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate))
|
|
default:
|
|
s.Equal(fasthttp.StatusProxyAuthRequired, mock.Ctx.Response.StatusCode())
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate))
|
|
s.Equal(`Basic realm="Authorization Required"`, string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate)))
|
|
}
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldApplyPolicyOfDenyDomain() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
authz := s.Builder().Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://deny.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Basic am9objpwYXNzd29yZA==")
|
|
|
|
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)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusForbidden, mock.Ctx.Response.StatusCode())
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate))
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate))
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldApplyPolicyOfOneFactorDomainWithAuthorizationHeader() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
// Equivalent of TestShouldVerifyAuthBasicArgOk.
|
|
|
|
builder := NewAuthzBuilder().WithImplementationLegacy()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewHeaderAuthorizationAuthnStrategy("basic"),
|
|
NewHeaderProxyAuthorizationAuthRequestAuthnStrategy("basic"),
|
|
NewCookieSessionAuthnStrategy(builder.config.RefreshInterval),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderAuthorization, "Basic am9objpwYXNzd29yZA==")
|
|
|
|
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)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate))
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate))
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldHandleAuthzWithoutHeaderNoCookie() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
// Equivalent of TestShouldVerifyAuthBasicArgFailingNoHeader.
|
|
|
|
builder := NewAuthzBuilder().WithImplementationLegacy()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewHeaderAuthorizationAuthnStrategy("basic"),
|
|
NewHeaderProxyAuthorizationAuthRequestAuthnStrategy("basic"),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
|
s.Equal(`Basic realm="Authorization Required"`, string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate)))
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate))
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldHandleAuthzWithEmptyAuthorizationHeader() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
// Equivalent of TestShouldVerifyAuthBasicArgFailingEmptyHeader.
|
|
|
|
builder := NewAuthzBuilder().WithImplementationLegacy()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewHeaderAuthorizationAuthnStrategy("basic"),
|
|
NewHeaderProxyAuthorizationAuthRequestAuthnStrategy("basic"),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderAuthorization, "")
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
|
s.Equal(`Basic realm="Authorization Required"`, string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate)))
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate))
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldHandleAuthzWithAuthorizationHeaderInvalidPassword() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
// Equivalent of TestShouldVerifyAuthBasicArgFailingWrongPassword.
|
|
|
|
builder := NewAuthzBuilder().WithImplementationLegacy()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewHeaderAuthorizationAuthnStrategy("basic"),
|
|
NewHeaderProxyAuthorizationAuthRequestAuthnStrategy("basic"),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderAuthorization, "Basic am9objpwYXNzd29yZA==")
|
|
|
|
mock.UserProviderMock.EXPECT().
|
|
CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
|
|
Return(false, nil)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
|
s.Equal(`Basic realm="Authorization Required"`, string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate)))
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate))
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldHandleAuthzWithIncorrectAuthHeader() { // TestShouldVerifyAuthBasicArgFailingWrongHeader.
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
builder := s.Builder()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewHeaderAuthorizationAuthnStrategy("basic"),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Basic am9objpwYXNzd29yZA==")
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
|
s.Equal(`Basic realm="Authorization Required"`, string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderWWWAuthenticate)))
|
|
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate))
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldDestroySessionWhenInactiveForTooLong() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
builder := s.Builder()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDuration(testInactivity)),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Clock = &mock.Clock
|
|
|
|
mock.Clock.Set(time.Now())
|
|
|
|
past := mock.Clock.Now().Add(-1 * time.Hour)
|
|
|
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
userSession, err := mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
userSession.Username = testUsername
|
|
userSession.AuthenticationLevel = authentication.TwoFactor
|
|
userSession.LastActivity = past.Unix()
|
|
|
|
s.Require().NoError(mock.Ctx.SaveSession(userSession))
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal("", userSession.Username)
|
|
s.Equal(authentication.NotAuthenticated, userSession.AuthenticationLevel)
|
|
s.Equal(mock.Clock.Now().Unix(), userSession.LastActivity)
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldNotDestroySessionWhenInactiveForTooLongRememberMe() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
builder := s.Builder()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDuration(testInactivity)),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Clock = &mock.Clock
|
|
|
|
mock.Clock.Set(time.Now())
|
|
|
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
userSession, err := mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
userSession.Username = testUsername
|
|
userSession.AuthenticationLevel = authentication.TwoFactor
|
|
userSession.LastActivity = 0
|
|
userSession.KeepMeLoggedIn = true
|
|
userSession.RefreshTTL = mock.Clock.Now().Add(5 * time.Minute)
|
|
|
|
s.Require().NoError(mock.Ctx.SaveSession(userSession))
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal(testUsername, userSession.Username)
|
|
s.Equal(authentication.TwoFactor, userSession.AuthenticationLevel)
|
|
s.Equal(int64(0), userSession.LastActivity)
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldNotDestroySessionWhenNotInactiveForTooLong() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
builder := s.Builder()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDuration(testInactivity)),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Clock = &mock.Clock
|
|
|
|
mock.Clock.Set(time.Now())
|
|
|
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
last := mock.Clock.Now().Add(-1 * time.Second)
|
|
|
|
userSession, err := mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
userSession.Username = testUsername
|
|
userSession.AuthenticationLevel = authentication.TwoFactor
|
|
userSession.LastActivity = last.Unix()
|
|
userSession.RefreshTTL = mock.Clock.Now().Add(5 * time.Minute)
|
|
|
|
s.Require().NoError(mock.Ctx.SaveSession(userSession))
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal(testUsername, userSession.Username)
|
|
s.Equal(authentication.TwoFactor, userSession.AuthenticationLevel)
|
|
s.Equal(mock.Clock.Now().Unix(), userSession.LastActivity)
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldUpdateInactivityTimestampEvenWhenHittingForbiddenResources() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
builder := s.Builder()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDuration(testInactivity)),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Clock = &mock.Clock
|
|
|
|
mock.Clock.Set(time.Now())
|
|
|
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://deny.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
last := mock.Clock.Now().Add(-3 * time.Second)
|
|
|
|
userSession, err := mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
userSession.Username = testUsername
|
|
userSession.AuthenticationLevel = authentication.TwoFactor
|
|
userSession.LastActivity = last.Unix()
|
|
userSession.RefreshTTL = mock.Clock.Now().Add(5 * time.Minute)
|
|
|
|
s.Require().NoError(mock.Ctx.SaveSession(userSession))
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal(testUsername, userSession.Username)
|
|
s.Equal(authentication.TwoFactor, userSession.AuthenticationLevel)
|
|
s.Equal(mock.Clock.Now().Unix(), userSession.LastActivity)
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldNotRefreshUserDetailsFromBackendWhenRefreshDisabled() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
builder := s.Builder()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDurationNever()),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
user := &authentication.UserDetails{
|
|
Username: "john",
|
|
Groups: []string{
|
|
"admin",
|
|
"users",
|
|
},
|
|
Emails: []string{
|
|
"john@example.com",
|
|
},
|
|
}
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
mock.Clock.Set(time.Now())
|
|
|
|
mock.Ctx.Clock = &mock.Clock
|
|
mock.Ctx.Configuration.AuthenticationBackend.RefreshInterval = schema.NewRefreshIntervalDurationNever()
|
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
userSession, err := mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
userSession.Username = user.Username
|
|
userSession.Groups = user.Groups
|
|
userSession.Emails = user.Emails
|
|
userSession.KeepMeLoggedIn = true
|
|
userSession.AuthenticationLevel = authentication.TwoFactor
|
|
userSession.LastActivity = mock.Clock.Now().Unix()
|
|
|
|
s.Require().NoError(mock.Ctx.SaveSession(userSession))
|
|
|
|
mock.UserProviderMock.EXPECT().GetDetails("john").Times(0)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
|
|
targetURI = s.RequireParseRequestURI("https://admin.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal(user.Username, userSession.Username)
|
|
s.Equal(authentication.TwoFactor, userSession.AuthenticationLevel)
|
|
s.Equal(mock.Clock.Now().Unix(), userSession.LastActivity)
|
|
s.Require().Len(userSession.Groups, 2)
|
|
s.Equal("admin", userSession.Groups[0])
|
|
s.Equal("users", userSession.Groups[1])
|
|
s.Equal(utils.RFC3339Zero, userSession.RefreshTTL.Unix())
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal(user.Username, userSession.Username)
|
|
s.Equal(authentication.TwoFactor, userSession.AuthenticationLevel)
|
|
s.Equal(mock.Clock.Now().Unix(), userSession.LastActivity)
|
|
s.Require().Len(userSession.Groups, 2)
|
|
s.Equal("admin", userSession.Groups[0])
|
|
s.Equal("users", userSession.Groups[1])
|
|
s.Equal(utils.RFC3339Zero, userSession.RefreshTTL.Unix())
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldDestroySessionWhenUserDoesNotExist() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
builder := s.Builder()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDuration(5 * time.Minute)),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Clock = &mock.Clock
|
|
|
|
mock.Clock.Set(time.Now())
|
|
|
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
user := &authentication.UserDetails{
|
|
Username: "john",
|
|
Groups: []string{
|
|
"admin",
|
|
"users",
|
|
},
|
|
Emails: []string{
|
|
"john@example.com",
|
|
},
|
|
}
|
|
|
|
userSession, err := mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
userSession.Username = user.Username
|
|
userSession.AuthenticationLevel = authentication.TwoFactor
|
|
userSession.LastActivity = mock.Clock.Now().Unix()
|
|
userSession.RefreshTTL = mock.Clock.Now().Add(-1 * time.Minute)
|
|
userSession.Groups = user.Groups
|
|
userSession.Emails = user.Emails
|
|
userSession.KeepMeLoggedIn = true
|
|
|
|
s.Require().NoError(mock.Ctx.SaveSession(userSession))
|
|
|
|
gomock.InOrder(
|
|
mock.UserProviderMock.EXPECT().GetDetails("john").Return(user, nil).Times(1),
|
|
mock.UserProviderMock.EXPECT().GetDetails("john").Return(nil, authentication.ErrUserNotFound).Times(1),
|
|
)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal(mock.Clock.Now().Add(5*time.Minute).Unix(), userSession.RefreshTTL.Unix())
|
|
|
|
userSession.RefreshTTL = mock.Clock.Now().Add(-1 * time.Minute)
|
|
|
|
s.Require().NoError(mock.Ctx.SaveSession(userSession))
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
switch s.implementation {
|
|
case AuthzImplAuthRequest, AuthzImplLegacy:
|
|
s.Equal(fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
|
default:
|
|
s.Equal(fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
|
}
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal("", userSession.Username)
|
|
s.Equal(authentication.NotAuthenticated, userSession.AuthenticationLevel)
|
|
s.True(userSession.IsAnonymous())
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldUpdateRemovedUserGroupsFromBackendAndDeny() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
builder := s.Builder()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDuration(5 * time.Minute)),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Clock = &mock.Clock
|
|
|
|
mock.Clock.Set(time.Now())
|
|
|
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://admin.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
user := &authentication.UserDetails{
|
|
Username: "john",
|
|
Groups: []string{
|
|
"admin",
|
|
"users",
|
|
},
|
|
Emails: []string{
|
|
"john@example.com",
|
|
},
|
|
}
|
|
|
|
userSession, err := mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
userSession.Username = user.Username
|
|
userSession.AuthenticationLevel = authentication.TwoFactor
|
|
userSession.LastActivity = mock.Clock.Now().Unix()
|
|
userSession.RefreshTTL = mock.Clock.Now().Add(-1 * time.Minute)
|
|
userSession.Groups = user.Groups
|
|
userSession.Emails = user.Emails
|
|
userSession.KeepMeLoggedIn = true
|
|
|
|
s.Require().NoError(mock.Ctx.SaveSession(userSession))
|
|
|
|
gomock.InOrder(
|
|
mock.UserProviderMock.EXPECT().GetDetails("john").Return(user, nil).Times(1),
|
|
mock.UserProviderMock.EXPECT().GetDetails("john").Return(user, nil).Times(1),
|
|
)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal(mock.Clock.Now().Add(5*time.Minute).Unix(), userSession.RefreshTTL.Unix())
|
|
s.Require().Len(userSession.Groups, 2)
|
|
s.Require().Equal("admin", userSession.Groups[0])
|
|
s.Require().Equal("users", userSession.Groups[1])
|
|
|
|
user.Groups = []string{"users"}
|
|
|
|
mock.Clock.Set(mock.Clock.Now().Add(6 * time.Minute))
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusForbidden, mock.Ctx.Response.StatusCode())
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal(mock.Clock.Now().Add(5*time.Minute).Unix(), userSession.RefreshTTL.Unix())
|
|
s.Require().Len(userSession.Groups, 1)
|
|
s.Require().Equal("users", userSession.Groups[0])
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldUpdateAddedUserGroupsFromBackendAndDeny() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
builder := s.Builder()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDuration(5 * time.Minute)),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Clock = &mock.Clock
|
|
|
|
mock.Clock.Set(time.Now())
|
|
|
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://admin.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
user := &authentication.UserDetails{
|
|
Username: "john",
|
|
Groups: []string{
|
|
"users",
|
|
},
|
|
Emails: []string{
|
|
"john@example.com",
|
|
},
|
|
}
|
|
|
|
userSession, err := mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
userSession.Username = user.Username
|
|
userSession.AuthenticationLevel = authentication.TwoFactor
|
|
userSession.LastActivity = mock.Clock.Now().Unix()
|
|
userSession.RefreshTTL = mock.Clock.Now().Add(-1 * time.Minute)
|
|
userSession.Groups = user.Groups
|
|
userSession.Emails = user.Emails
|
|
userSession.KeepMeLoggedIn = true
|
|
|
|
s.Require().NoError(mock.Ctx.SaveSession(userSession))
|
|
|
|
gomock.InOrder(
|
|
mock.UserProviderMock.EXPECT().GetDetails("john").Return(user, nil).Times(1),
|
|
mock.UserProviderMock.EXPECT().GetDetails("john").Return(user, nil).Times(1),
|
|
)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusForbidden, mock.Ctx.Response.StatusCode())
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal(mock.Clock.Now().Add(5*time.Minute).Unix(), userSession.RefreshTTL.Unix())
|
|
s.Require().Len(userSession.Groups, 1)
|
|
s.Require().Equal("users", userSession.Groups[0])
|
|
|
|
user.Groups = []string{"admin", "users"}
|
|
|
|
mock.Clock.Set(mock.Clock.Now().Add(6 * time.Minute))
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal(mock.Clock.Now().Add(5*time.Minute).Unix(), userSession.RefreshTTL.Unix())
|
|
s.Require().Len(userSession.Groups, 2)
|
|
s.Require().Equal("admin", userSession.Groups[0])
|
|
s.Require().Equal("users", userSession.Groups[1])
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldCheckValidSessionUsernameHeaderAndReturn200() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
builder := s.Builder()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDuration(testInactivity)),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Clock = &mock.Clock
|
|
|
|
mock.Clock.Set(time.Now())
|
|
|
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.SetBytesK(headerSessionUsername, testUsername)
|
|
|
|
userSession, err := mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
userSession.Username = testUsername
|
|
userSession.AuthenticationLevel = authentication.OneFactor
|
|
userSession.LastActivity = mock.Clock.Now().Unix()
|
|
userSession.RefreshTTL = mock.Clock.Now().Add(5 * time.Minute)
|
|
|
|
s.Require().NoError(mock.Ctx.SaveSession(userSession))
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal(testUsername, userSession.Username)
|
|
s.Equal(authentication.OneFactor, userSession.AuthenticationLevel)
|
|
s.Equal(mock.Clock.Now().Unix(), userSession.LastActivity)
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldCheckInvalidSessionUsernameHeaderAndReturn401AndDestroySession() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
builder := s.Builder()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDuration(5 * time.Minute)),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Clock = &mock.Clock
|
|
|
|
mock.Clock.Set(time.Now())
|
|
|
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
mock.Ctx.Request.Header.SetBytesK(headerSessionUsername, "root")
|
|
|
|
userSession, err := mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
userSession.Username = testUsername
|
|
userSession.AuthenticationLevel = authentication.OneFactor
|
|
userSession.LastActivity = mock.Clock.Now().Unix()
|
|
userSession.RefreshTTL = mock.Clock.Now().Add(5 * time.Minute)
|
|
|
|
s.Require().NoError(mock.Ctx.SaveSession(userSession))
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
switch s.implementation {
|
|
case AuthzImplAuthRequest, AuthzImplLegacy:
|
|
s.Equal(fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
|
default:
|
|
s.Equal(fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
|
location := s.RequireParseRequestURI(mock.Ctx.Configuration.Session.Cookies[0].AutheliaURL.String())
|
|
|
|
if location.Path == "" {
|
|
location.Path = "/"
|
|
}
|
|
|
|
query := location.Query()
|
|
query.Set(queryArgRD, targetURI.String())
|
|
query.Set(queryArgRM, fasthttp.MethodGet)
|
|
|
|
location.RawQuery = query.Encode()
|
|
|
|
s.Equal(location.String(), string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
|
}
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal("", userSession.Username)
|
|
s.Equal(authentication.NotAuthenticated, userSession.AuthenticationLevel)
|
|
s.Equal(mock.Clock.Now().Unix(), userSession.LastActivity)
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldNotRedirectRequestsForBypassACLWhenInactiveForTooLong() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
builder := s.Builder()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDuration(testInactivity)),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Clock = &mock.Clock
|
|
|
|
mock.Clock.Set(time.Now())
|
|
|
|
past := mock.Clock.Now().Add(-24 * time.Hour)
|
|
|
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
userSession, err := mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
userSession.Username = testUsername
|
|
userSession.AuthenticationLevel = authentication.TwoFactor
|
|
userSession.LastActivity = past.Unix()
|
|
|
|
s.Require().NoError(mock.Ctx.SaveSession(userSession))
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
|
|
|
userSession, err = mock.Ctx.GetSession()
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal("", userSession.Username)
|
|
s.Equal(authentication.NotAuthenticated, userSession.AuthenticationLevel)
|
|
s.Equal(mock.Clock.Now().Unix(), userSession.LastActivity)
|
|
|
|
targetURI = s.RequireParseRequestURI("https://two-factor.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
switch s.implementation {
|
|
case AuthzImplAuthRequest, AuthzImplLegacy:
|
|
s.Equal(fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
|
default:
|
|
s.Equal(fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
|
location := s.RequireParseRequestURI(mock.Ctx.Configuration.Session.Cookies[0].AutheliaURL.String())
|
|
|
|
if location.Path == "" {
|
|
location.Path = "/"
|
|
}
|
|
|
|
query := location.Query()
|
|
query.Set(queryArgRD, targetURI.String())
|
|
query.Set(queryArgRM, fasthttp.MethodGet)
|
|
|
|
location.RawQuery = query.Encode()
|
|
|
|
s.Equal(location.String(), string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
|
}
|
|
}
|
|
|
|
func (s *AuthzSuite) TestShouldFailToParsePortalURL() {
|
|
if s.setRequest == nil {
|
|
s.T().Skip()
|
|
}
|
|
|
|
builder := s.Builder()
|
|
|
|
builder = builder.WithStrategies(
|
|
NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDuration(testInactivity)),
|
|
)
|
|
|
|
authz := builder.Build()
|
|
|
|
mock := mocks.NewMockAutheliaCtx(s.T())
|
|
|
|
defer mock.Close()
|
|
|
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
|
|
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
|
|
|
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
|
|
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
|
|
|
expected := fasthttp.StatusBadRequest
|
|
|
|
switch s.implementation {
|
|
case AuthzImplLegacy:
|
|
expected = fasthttp.StatusUnauthorized
|
|
|
|
mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, "JKL$#N%KJ#@$N")
|
|
case AuthzImplForwardAuth, AuthzImplAuthRequest:
|
|
mock.Ctx.RequestCtx.QueryArgs().Set("authelia_url", "JKL$#N%KJ#@$N")
|
|
case AuthzImplExtAuthz:
|
|
mock.Ctx.Request.Header.Set("X-Authelia-URL", "JKL$#N%KJ#@$N")
|
|
}
|
|
|
|
authz.Handler(mock.Ctx)
|
|
|
|
s.Equal(expected, mock.Ctx.Response.StatusCode())
|
|
s.Equal(fmt.Sprintf("%d %s", expected, fasthttp.StatusMessage(expected)), string(mock.Ctx.Response.Body()))
|
|
s.Equal("", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
|
s.Equal("text/plain; charset=utf-8", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderContentType)))
|
|
}
|
|
|
|
func setRequestXHRValues(ctx *middlewares.AutheliaCtx, accept, xhr bool) {
|
|
if accept {
|
|
ctx.Request.Header.Set(fasthttp.HeaderAccept, "text/html; charset=utf-8")
|
|
}
|
|
|
|
if xhr {
|
|
ctx.Request.Header.Set(fasthttp.HeaderXRequestedWith, "XMLHttpRequest")
|
|
}
|
|
}
|
|
|
|
type urlpair struct {
|
|
TargetURI *url.URL
|
|
AutheliaURI *url.URL
|
|
}
|