feat(authentication): ldap time replacements (#4483)
This adds and utilizes several time replacements for both specialized LDAP implementations. Closes #1964, Closes #1284pull/4498/head^2
parent
d0d80b4f66
commit
d67554ab88
|
@ -61,11 +61,14 @@ search.
|
||||||
#### Users filter replacements
|
#### Users filter replacements
|
||||||
|
|
||||||
| Placeholder | Phase | Replacement |
|
| Placeholder | Phase | Replacement |
|
||||||
|:------------------------:|:-------:|:-------------------------------------:|
|
|:-------------------------:|:-------:|:--------------------------------------------------------------------------------------------------------------:|
|
||||||
| {username_attribute} | startup | The configured username attribute |
|
| {username_attribute} | startup | The configured username attribute |
|
||||||
| {mail_attribute} | startup | The configured mail attribute |
|
| {mail_attribute} | startup | The configured mail attribute |
|
||||||
| {display_name_attribute} | startup | The configured display name attribute |
|
| {display_name_attribute} | startup | The configured display name attribute |
|
||||||
| {input} | search | The input into the username field |
|
| {input} | search | The input into the username field |
|
||||||
|
| {date-time:generalized} | search | The current UTC time formatted as a LDAP generalized time in the format of `20060102150405.0Z` |
|
||||||
|
| {date-time:unix-epoch} | search | The current time formatted as a Unix epoch |
|
||||||
|
| {date-time:msft-nt-epoch} | search | The current time formatted as a Microsoft NT epoch which is used by some Microsoft Active Directory attributes |
|
||||||
|
|
||||||
#### Groups filter replacements
|
#### Groups filter replacements
|
||||||
|
|
||||||
|
@ -92,16 +95,24 @@ Username column.
|
||||||
|
|
||||||
#### Filter defaults
|
#### Filter defaults
|
||||||
|
|
||||||
The filters are probably the most important part to get correct when setting up LDAP. You want to exclude disabled
|
The filters are probably the most important part to get correct when setting up LDAP. You want to exclude accounts under
|
||||||
accounts. The active directory example has two attribute filters that accomplish this as an example (more examples would
|
the following conditions:
|
||||||
be appreciated). The userAccountControl filter checks that the account is not disabled and the pwdLastSet makes sure that
|
|
||||||
value is not 0 which means the password requires changing at the next login.
|
- The account is disabled or locked:
|
||||||
|
- The Active Directory implementation achieves this via the `(!(userAccountControl:1.2.840.113556.1.4.803:=2))` filter.
|
||||||
|
- The FreeIPA implementation achieves this via the `(!(nsAccountLock=TRUE))` filter.
|
||||||
|
- Their password is expired:
|
||||||
|
- The Active Directory implementation achieves this via the `(!(pwdLastSet=0))` filter.
|
||||||
|
- The FreeIPA implementation achieves this via the `(krbPasswordExpiration>={date-time:generalized})` filter.
|
||||||
|
- Their account is expired:
|
||||||
|
- The Active Directory implementation achieves this via the `(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:msft-nt-epoch}))` filter.
|
||||||
|
- The FreeIPA implementation achieves this via the `(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized}))` filter.
|
||||||
|
|
||||||
| Implementation | Users Filter | Groups Filter |
|
| Implementation | Users Filter | Groups Filter |
|
||||||
|:---------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------:|
|
|:---------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------:|
|
||||||
| custom | N/A | N/A |
|
| custom | N/A | N/A |
|
||||||
| activedirectory | (&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))) | (&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912))) |
|
| activedirectory | (&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:msft-nt-epoch}))) | (&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912))) |
|
||||||
| freeipa | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))) | (&(member={dn})(objectClass=groupOfNames)) |
|
| freeipa | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized}))) | (&(member={dn})(objectClass=groupOfNames)) |
|
||||||
|
|
||||||
##### Microsoft Active Directory sAMAccountType
|
##### Microsoft Active Directory sAMAccountType
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,13 @@ const (
|
||||||
ldapPlaceholderInput = "{input}"
|
ldapPlaceholderInput = "{input}"
|
||||||
ldapPlaceholderDistinguishedName = "{dn}"
|
ldapPlaceholderDistinguishedName = "{dn}"
|
||||||
ldapPlaceholderUsername = "{username}"
|
ldapPlaceholderUsername = "{username}"
|
||||||
|
ldapPlaceholderDateTimeGeneralized = "{date-time:generalized}"
|
||||||
|
ldapPlaceholderDateTimeMicrosoftNTTimeEpoch = "{date-time:msft-nt-epoch}"
|
||||||
|
ldapPlaceholderDateTimeUnixEpoch = "{date-time:unix-epoch}"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ldapGeneralizedTimeDateTimeFormat = "20060102150405.0Z"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
@ -23,6 +24,8 @@ type LDAPUserProvider struct {
|
||||||
log *logrus.Logger
|
log *logrus.Logger
|
||||||
factory LDAPClientFactory
|
factory LDAPClientFactory
|
||||||
|
|
||||||
|
clock utils.Clock
|
||||||
|
|
||||||
disableResetPassword bool
|
disableResetPassword bool
|
||||||
|
|
||||||
// Automatically detected LDAP features.
|
// Automatically detected LDAP features.
|
||||||
|
@ -32,6 +35,9 @@ type LDAPUserProvider struct {
|
||||||
usersBaseDN string
|
usersBaseDN string
|
||||||
usersAttributes []string
|
usersAttributes []string
|
||||||
usersFilterReplacementInput bool
|
usersFilterReplacementInput bool
|
||||||
|
usersFilterReplacementDateTimeGeneralized bool
|
||||||
|
usersFilterReplacementDateTimeUnixEpoch bool
|
||||||
|
usersFilterReplacementDateTimeMicrosoftNTTimeEpoch bool
|
||||||
|
|
||||||
// Dynamically generated groups values.
|
// Dynamically generated groups values.
|
||||||
groupsBaseDN string
|
groupsBaseDN string
|
||||||
|
@ -41,14 +47,15 @@ type LDAPUserProvider struct {
|
||||||
groupsFilterReplacementDN bool
|
groupsFilterReplacementDN bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLDAPUserProvider creates a new instance of LDAPUserProvider.
|
// NewLDAPUserProvider creates a new instance of LDAPUserProvider with the ProductionLDAPClientFactory.
|
||||||
func NewLDAPUserProvider(config schema.AuthenticationBackend, certPool *x509.CertPool) (provider *LDAPUserProvider) {
|
func NewLDAPUserProvider(config schema.AuthenticationBackend, certPool *x509.CertPool) (provider *LDAPUserProvider) {
|
||||||
provider = newLDAPUserProvider(*config.LDAP, config.PasswordReset.Disable, certPool, nil)
|
provider = NewLDAPUserProviderWithFactory(*config.LDAP, config.PasswordReset.Disable, certPool, NewProductionLDAPClientFactory())
|
||||||
|
|
||||||
return provider
|
return provider
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLDAPUserProvider(config schema.LDAPAuthenticationBackend, disableResetPassword bool, certPool *x509.CertPool, factory LDAPClientFactory) (provider *LDAPUserProvider) {
|
// NewLDAPUserProviderWithFactory creates a new instance of LDAPUserProvider with the specified LDAPClientFactory.
|
||||||
|
func NewLDAPUserProviderWithFactory(config schema.LDAPAuthenticationBackend, disableResetPassword bool, certPool *x509.CertPool, factory LDAPClientFactory) (provider *LDAPUserProvider) {
|
||||||
if config.TLS == nil {
|
if config.TLS == nil {
|
||||||
config.TLS = schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.TLS
|
config.TLS = schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.TLS
|
||||||
}
|
}
|
||||||
|
@ -74,6 +81,7 @@ func newLDAPUserProvider(config schema.LDAPAuthenticationBackend, disableResetPa
|
||||||
log: logging.Logger(),
|
log: logging.Logger(),
|
||||||
factory: factory,
|
factory: factory,
|
||||||
disableResetPassword: disableResetPassword,
|
disableResetPassword: disableResetPassword,
|
||||||
|
clock: &utils.RealClock{},
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.parseDynamicUsersConfiguration()
|
provider.parseDynamicUsersConfiguration()
|
||||||
|
@ -394,12 +402,24 @@ func (p *LDAPUserProvider) getUserProfile(client LDAPClient, username string) (p
|
||||||
return &userProfile, nil
|
return &userProfile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *LDAPUserProvider) resolveUsersFilter(username string) (filter string) {
|
func (p *LDAPUserProvider) resolveUsersFilter(input string) (filter string) {
|
||||||
filter = p.config.UsersFilter
|
filter = p.config.UsersFilter
|
||||||
|
|
||||||
if p.usersFilterReplacementInput {
|
if p.usersFilterReplacementInput {
|
||||||
// The {input} placeholder is replaced by the username input.
|
// The {input} placeholder is replaced by the username input.
|
||||||
filter = strings.ReplaceAll(filter, ldapPlaceholderInput, ldapEscape(username))
|
filter = strings.ReplaceAll(filter, ldapPlaceholderInput, ldapEscape(input))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.usersFilterReplacementDateTimeGeneralized {
|
||||||
|
filter = strings.ReplaceAll(filter, ldapPlaceholderDateTimeGeneralized, p.clock.Now().UTC().Format(ldapGeneralizedTimeDateTimeFormat))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.usersFilterReplacementDateTimeUnixEpoch {
|
||||||
|
filter = strings.ReplaceAll(filter, ldapPlaceholderDateTimeUnixEpoch, strconv.Itoa(int(p.clock.Now().Unix())))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.usersFilterReplacementDateTimeMicrosoftNTTimeEpoch {
|
||||||
|
filter = strings.ReplaceAll(filter, ldapPlaceholderDateTimeMicrosoftNTTimeEpoch, strconv.Itoa(int(utils.UnixNanoTimeToMicrosoftNTEpoch(p.clock.Now().UnixNano()))))
|
||||||
}
|
}
|
||||||
|
|
||||||
p.log.Tracef("Detected user filter is %s", filter)
|
p.log.Tracef("Detected user filter is %s", filter)
|
||||||
|
@ -407,12 +427,12 @@ func (p *LDAPUserProvider) resolveUsersFilter(username string) (filter string) {
|
||||||
return filter
|
return filter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *LDAPUserProvider) resolveGroupsFilter(username string, profile *ldapUserProfile) (filter string) {
|
func (p *LDAPUserProvider) resolveGroupsFilter(input string, profile *ldapUserProfile) (filter string) {
|
||||||
filter = p.config.GroupsFilter
|
filter = p.config.GroupsFilter
|
||||||
|
|
||||||
if p.groupsFilterReplacementInput {
|
if p.groupsFilterReplacementInput {
|
||||||
// The {input} placeholder is replaced by the users username input.
|
// The {input} placeholder is replaced by the users username input.
|
||||||
filter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderInput, ldapEscape(username))
|
filter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderInput, ldapEscape(input))
|
||||||
}
|
}
|
||||||
|
|
||||||
if profile != nil {
|
if profile != nil {
|
||||||
|
|
|
@ -120,6 +120,18 @@ func (p *LDAPUserProvider) parseDynamicUsersConfiguration() {
|
||||||
p.usersFilterReplacementInput = true
|
p.usersFilterReplacementInput = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.Contains(p.config.UsersFilter, ldapPlaceholderDateTimeGeneralized) {
|
||||||
|
p.usersFilterReplacementDateTimeGeneralized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(p.config.UsersFilter, ldapPlaceholderDateTimeUnixEpoch) {
|
||||||
|
p.usersFilterReplacementDateTimeUnixEpoch = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(p.config.UsersFilter, ldapPlaceholderDateTimeMicrosoftNTTimeEpoch) {
|
||||||
|
p.usersFilterReplacementDateTimeMicrosoftNTTimeEpoch = true
|
||||||
|
}
|
||||||
|
|
||||||
p.log.Tracef("Detected user filter replacements that need to be resolved per lookup are: %s=%v",
|
p.log.Tracef("Detected user filter replacements that need to be resolved per lookup are: %s=%v",
|
||||||
ldapPlaceholderInput, p.usersFilterReplacementInput)
|
ldapPlaceholderInput, p.usersFilterReplacementInput)
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -187,7 +187,7 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationCustom = LDAPAuth
|
||||||
|
|
||||||
// DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory represents the default LDAP config for the LDAPImplementationActiveDirectory Implementation.
|
// DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory represents the default LDAP config for the LDAPImplementationActiveDirectory Implementation.
|
||||||
var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory = LDAPAuthenticationBackend{
|
var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory = LDAPAuthenticationBackend{
|
||||||
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0)))",
|
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:msft-nt-epoch})))",
|
||||||
UsernameAttribute: "sAMAccountName",
|
UsernameAttribute: "sAMAccountName",
|
||||||
MailAttribute: ldapAttrMail,
|
MailAttribute: ldapAttrMail,
|
||||||
DisplayNameAttribute: ldapAttrDisplayName,
|
DisplayNameAttribute: ldapAttrDisplayName,
|
||||||
|
@ -201,7 +201,7 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory =
|
||||||
|
|
||||||
// DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA represents the default LDAP config for the LDAPImplementationFreeIPA Implementation.
|
// DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA represents the default LDAP config for the LDAPImplementationFreeIPA Implementation.
|
||||||
var DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA = LDAPAuthenticationBackend{
|
var DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA = LDAPAuthenticationBackend{
|
||||||
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE)))",
|
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized})))",
|
||||||
UsernameAttribute: ldapAttrUserID,
|
UsernameAttribute: ldapAttrUserID,
|
||||||
MailAttribute: ldapAttrMail,
|
MailAttribute: ldapAttrMail,
|
||||||
DisplayNameAttribute: ldapAttrDisplayName,
|
DisplayNameAttribute: ldapAttrDisplayName,
|
||||||
|
|
|
@ -702,7 +702,7 @@ func TestShouldDestroySessionWhenInactiveForTooLong(t *testing.T) {
|
||||||
mock := mocks.NewMockAutheliaCtx(t)
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
clock := mocks.TestingClock{}
|
clock := utils.TestingClock{}
|
||||||
clock.Set(time.Now())
|
clock.Set(time.Now())
|
||||||
past := clock.Now().Add(-1 * time.Hour)
|
past := clock.Now().Add(-1 * time.Hour)
|
||||||
|
|
||||||
|
@ -736,7 +736,7 @@ func TestShouldDestroySessionWhenInactiveForTooLongUsingDurationNotation(t *test
|
||||||
mock := mocks.NewMockAutheliaCtx(t)
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
clock := mocks.TestingClock{}
|
clock := utils.TestingClock{}
|
||||||
clock.Set(time.Now())
|
clock.Set(time.Now())
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Inactivity = time.Second * 10
|
mock.Ctx.Configuration.Session.Inactivity = time.Second * 10
|
||||||
|
@ -833,7 +833,7 @@ func TestShouldRedirectWhenSessionInactiveForTooLongAndRDParamProvided(t *testin
|
||||||
mock := mocks.NewMockAutheliaCtx(t)
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
clock := mocks.TestingClock{}
|
clock := utils.TestingClock{}
|
||||||
clock.Set(time.Now())
|
clock.Set(time.Now())
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
||||||
|
@ -1055,7 +1055,7 @@ func TestShouldNotRefreshUserGroupsFromBackend(t *testing.T) {
|
||||||
|
|
||||||
mock.UserProviderMock.EXPECT().GetDetails("john").Times(0)
|
mock.UserProviderMock.EXPECT().GetDetails("john").Times(0)
|
||||||
|
|
||||||
clock := mocks.TestingClock{}
|
clock := utils.TestingClock{}
|
||||||
clock.Set(time.Now())
|
clock.Set(time.Now())
|
||||||
|
|
||||||
userSession := mock.Ctx.GetSession()
|
userSession := mock.Ctx.GetSession()
|
||||||
|
@ -1115,7 +1115,7 @@ func TestShouldNotRefreshUserGroupsFromBackendWhenDisabled(t *testing.T) {
|
||||||
|
|
||||||
mock.UserProviderMock.EXPECT().GetDetails("john").Times(0)
|
mock.UserProviderMock.EXPECT().GetDetails("john").Times(0)
|
||||||
|
|
||||||
clock := mocks.TestingClock{}
|
clock := utils.TestingClock{}
|
||||||
clock.Set(time.Now())
|
clock.Set(time.Now())
|
||||||
|
|
||||||
userSession := mock.Ctx.GetSession()
|
userSession := mock.Ctx.GetSession()
|
||||||
|
@ -1161,7 +1161,7 @@ func TestShouldDestroySessionWhenUserNotExist(t *testing.T) {
|
||||||
|
|
||||||
mock.UserProviderMock.EXPECT().GetDetails("john").Return(user, nil).Times(1)
|
mock.UserProviderMock.EXPECT().GetDetails("john").Return(user, nil).Times(1)
|
||||||
|
|
||||||
clock := mocks.TestingClock{}
|
clock := utils.TestingClock{}
|
||||||
clock.Set(time.Now())
|
clock.Set(time.Now())
|
||||||
|
|
||||||
userSession := mock.Ctx.GetSession()
|
userSession := mock.Ctx.GetSession()
|
||||||
|
@ -1222,7 +1222,7 @@ func TestShouldGetRemovedUserGroupsFromBackend(t *testing.T) {
|
||||||
|
|
||||||
mock.UserProviderMock.EXPECT().GetDetails("john").Return(user, nil).Times(2)
|
mock.UserProviderMock.EXPECT().GetDetails("john").Return(user, nil).Times(2)
|
||||||
|
|
||||||
clock := mocks.TestingClock{}
|
clock := utils.TestingClock{}
|
||||||
clock.Set(time.Now())
|
clock.Set(time.Now())
|
||||||
|
|
||||||
userSession := mock.Ctx.GetSession()
|
userSession := mock.Ctx.GetSession()
|
||||||
|
@ -1431,7 +1431,7 @@ func TestShouldNotRedirectRequestsForBypassACLWhenInactiveForTooLong(t *testing.
|
||||||
mock := mocks.NewMockAutheliaCtx(t)
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
clock := mocks.TestingClock{}
|
clock := utils.TestingClock{}
|
||||||
clock.Set(time.Now())
|
clock.Set(time.Now())
|
||||||
past := clock.Now().Add(-1 * time.Hour)
|
past := clock.Now().Add(-1 * time.Hour)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/regulation"
|
"github.com/authelia/authelia/v4/internal/regulation"
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
"github.com/authelia/authelia/v4/internal/templates"
|
"github.com/authelia/authelia/v4/internal/templates"
|
||||||
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockAutheliaCtx a mock of AutheliaCtx.
|
// MockAutheliaCtx a mock of AutheliaCtx.
|
||||||
|
@ -36,33 +37,13 @@ type MockAutheliaCtx struct {
|
||||||
|
|
||||||
UserSession *session.UserSession
|
UserSession *session.UserSession
|
||||||
|
|
||||||
Clock TestingClock
|
Clock utils.TestingClock
|
||||||
}
|
|
||||||
|
|
||||||
// TestingClock implementation of clock for tests.
|
|
||||||
type TestingClock struct {
|
|
||||||
now time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now return the stored clock.
|
|
||||||
func (dc *TestingClock) Now() time.Time {
|
|
||||||
return dc.now
|
|
||||||
}
|
|
||||||
|
|
||||||
// After return a channel receiving the time after duration has elapsed.
|
|
||||||
func (dc *TestingClock) After(d time.Duration) <-chan time.Time {
|
|
||||||
return time.After(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set set the time of the clock.
|
|
||||||
func (dc *TestingClock) Set(now time.Time) {
|
|
||||||
dc.now = now
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockAutheliaCtx create an instance of AutheliaCtx mock.
|
// NewMockAutheliaCtx create an instance of AutheliaCtx mock.
|
||||||
func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
|
func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
|
||||||
mockAuthelia := new(MockAutheliaCtx)
|
mockAuthelia := new(MockAutheliaCtx)
|
||||||
mockAuthelia.Clock = TestingClock{}
|
mockAuthelia.Clock = utils.TestingClock{}
|
||||||
|
|
||||||
datetime, _ := time.Parse("2006-Jan-02", "2013-Feb-03")
|
datetime, _ := time.Parse("2006-Jan-02", "2013-Feb-03")
|
||||||
mockAuthelia.Clock.Set(datetime)
|
mockAuthelia.Clock.Set(datetime)
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/mocks"
|
"github.com/authelia/authelia/v4/internal/mocks"
|
||||||
"github.com/authelia/authelia/v4/internal/model"
|
"github.com/authelia/authelia/v4/internal/model"
|
||||||
"github.com/authelia/authelia/v4/internal/regulation"
|
"github.com/authelia/authelia/v4/internal/regulation"
|
||||||
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RegulatorSuite struct {
|
type RegulatorSuite struct {
|
||||||
|
@ -22,7 +23,7 @@ type RegulatorSuite struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
storageMock *mocks.MockStorage
|
storageMock *mocks.MockStorage
|
||||||
config schema.RegulationConfiguration
|
config schema.RegulationConfiguration
|
||||||
clock mocks.TestingClock
|
clock utils.TestingClock
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RegulatorSuite) SetupTest() {
|
func (s *RegulatorSuite) SetupTest() {
|
||||||
|
|
|
@ -20,3 +20,23 @@ func (RealClock) Now() time.Time {
|
||||||
func (RealClock) After(d time.Duration) <-chan time.Time {
|
func (RealClock) After(d time.Duration) <-chan time.Time {
|
||||||
return time.After(d)
|
return time.After(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestingClock implementation of clock for tests.
|
||||||
|
type TestingClock struct {
|
||||||
|
now time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now return the stored clock.
|
||||||
|
func (dc *TestingClock) Now() time.Time {
|
||||||
|
return dc.now
|
||||||
|
}
|
||||||
|
|
||||||
|
// After return a channel receiving the time after duration has elapsed.
|
||||||
|
func (dc *TestingClock) After(d time.Duration) <-chan time.Time {
|
||||||
|
return time.After(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set set the time of the clock.
|
||||||
|
func (dc *TestingClock) Set(now time.Time) {
|
||||||
|
dc.now = now
|
||||||
|
}
|
||||||
|
|
|
@ -8,24 +8,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
windows = "windows"
|
|
||||||
testStringInput = "abcdefghijkl"
|
|
||||||
|
|
||||||
// RFC3339Zero is the default value for time.Time.Unix().
|
// RFC3339Zero is the default value for time.Time.Unix().
|
||||||
RFC3339Zero = int64(-62135596800)
|
RFC3339Zero = int64(-62135596800)
|
||||||
|
|
||||||
// TLS13 is the textual representation of TLS 1.3.
|
|
||||||
TLS13 = "1.3"
|
|
||||||
|
|
||||||
// TLS12 is the textual representation of TLS 1.2.
|
|
||||||
TLS12 = "1.2"
|
|
||||||
|
|
||||||
// TLS11 is the textual representation of TLS 1.1.
|
|
||||||
TLS11 = "1.1"
|
|
||||||
|
|
||||||
// TLS10 is the textual representation of TLS 1.0.
|
|
||||||
TLS10 = "1.0"
|
|
||||||
|
|
||||||
clean = "clean"
|
clean = "clean"
|
||||||
tagged = "tagged"
|
tagged = "tagged"
|
||||||
unknown = "unknown"
|
unknown = "unknown"
|
||||||
|
@ -84,10 +69,6 @@ const (
|
||||||
Month = Year / 12
|
Month = Year / 12
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
errFmtLinuxNotFound = "open %s: no such file or directory"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
standardDurationUnits = []string{"ns", "us", "µs", "μs", "ms", "s", "m", "h"}
|
standardDurationUnits = []string{"ns", "us", "µs", "μs", "ms", "s", "m", "h"}
|
||||||
reDurationSeconds = regexp.MustCompile(`^\d+$`)
|
reDurationSeconds = regexp.MustCompile(`^\d+$`)
|
||||||
|
@ -110,6 +91,12 @@ const (
|
||||||
HoursInYear = HoursInDay * 365
|
HoursInYear = HoursInDay * 365
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// timeUnixEpochAsMicrosoftNTEpoch represents the unix epoch as a Microsoft NT Epoch.
|
||||||
|
// The Microsoft NT Epoch is ticks since Jan 1, 1601 (1 tick is 100ns).
|
||||||
|
timeUnixEpochAsMicrosoftNTEpoch uint64 = 116444736000000000
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// CharSetAlphabeticLower are literally just valid alphabetic lowercase printable ASCII chars.
|
// CharSetAlphabeticLower are literally just valid alphabetic lowercase printable ASCII chars.
|
||||||
CharSetAlphabeticLower = "abcdefghijklmnopqrstuvwxyz"
|
CharSetAlphabeticLower = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
@ -155,5 +142,7 @@ var htmlEscaper = strings.NewReplacer(
|
||||||
// ErrTimeoutReached error thrown when a timeout is reached.
|
// ErrTimeoutReached error thrown when a timeout is reached.
|
||||||
var ErrTimeoutReached = errors.New("timeout reached")
|
var ErrTimeoutReached = errors.New("timeout reached")
|
||||||
|
|
||||||
// ErrTLSVersionNotSupported returned when an unknown TLS version supplied.
|
const (
|
||||||
var ErrTLSVersionNotSupported = errors.New("supplied tls version isn't supported")
|
windows = "windows"
|
||||||
|
errFmtLinuxNotFound = "open %s: no such file or directory"
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
const (
|
||||||
|
testStringInput = "abcdefghijkl"
|
||||||
|
)
|
|
@ -67,3 +67,8 @@ func ParseDurationString(input string) (duration time.Duration, err error) {
|
||||||
|
|
||||||
return time.ParseDuration(out)
|
return time.ParseDuration(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnixNanoTimeToMicrosoftNTEpoch converts a unix timestamp in nanosecond format to win32 epoch format.
|
||||||
|
func UnixNanoTimeToMicrosoftNTEpoch(nano int64) (t uint64) {
|
||||||
|
return uint64(nano/100) + timeUnixEpochAsMicrosoftNTEpoch
|
||||||
|
}
|
||||||
|
|
|
@ -122,3 +122,11 @@ func TestShouldTimeIntervalsMakeSense(t *testing.T) {
|
||||||
assert.Equal(t, Year, Day*365)
|
assert.Equal(t, Year, Day*365)
|
||||||
assert.Equal(t, Month, Year/12)
|
assert.Equal(t, Month, Year/12)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldConvertKnownUnixNanoTimeToKnownWin32Epoch(t *testing.T) {
|
||||||
|
exampleNanoTime := int64(1626234411 * 1000000000)
|
||||||
|
win32Epoch := uint64(132707080110000000)
|
||||||
|
|
||||||
|
assert.Equal(t, win32Epoch, UnixNanoTimeToMicrosoftNTEpoch(exampleNanoTime))
|
||||||
|
assert.Equal(t, timeUnixEpochAsMicrosoftNTEpoch, UnixNanoTimeToMicrosoftNTEpoch(0))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue