summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/authentication/cached.go106
-rw-r--r--internal/authentication/cached_test.go35
-rw-r--r--internal/configuration/schema/keys.go1
-rw-r--r--internal/configuration/schema/server.go5
-rw-r--r--internal/configuration/validator/const.go1
-rw-r--r--internal/configuration/validator/server.go4
-rw-r--r--internal/configuration/validator/server_test.go9
-rw-r--r--internal/handlers/handler_authz_authn.go92
-rw-r--r--internal/handlers/handler_authz_builder.go12
-rw-r--r--internal/handlers/handler_authz_builder_test.go4
-rw-r--r--internal/handlers/handler_authz_test.go495
11 files changed, 717 insertions, 47 deletions
diff --git a/internal/authentication/cached.go b/internal/authentication/cached.go
new file mode 100644
index 000000000..b84d988bf
--- /dev/null
+++ b/internal/authentication/cached.go
@@ -0,0 +1,106 @@
+package authentication
+
+import (
+ "crypto/hmac"
+ "crypto/rand"
+ "fmt"
+ "hash"
+ "sync"
+ "time"
+)
+
+// NewCredentialCacheHMAC creates a new CredentialCacheHMAC with a given hash.Hash func and lifespan.
+func NewCredentialCacheHMAC(h func() hash.Hash, lifespan time.Duration) *CredentialCacheHMAC {
+ secret := make([]byte, h().BlockSize())
+
+ _, _ = rand.Read(secret)
+
+ return &CredentialCacheHMAC{
+ mu: sync.Mutex{},
+ hash: hmac.New(h, secret),
+ lifespan: lifespan,
+
+ values: map[string]CachedCredential{},
+ }
+}
+
+// CredentialCacheHMAC implements in-memory credential caching using a HMAC function and effective lifespan.
+type CredentialCacheHMAC struct {
+ mu sync.Mutex
+ hash hash.Hash
+
+ lifespan time.Duration
+
+ values map[string]CachedCredential
+}
+
+// Valid checks the cache for results for a given username and password in the cache and returns two booleans. The valid
+// return value is indicative if the credential cache had an exact match, and the ok return value returns true if a
+// current cached value exists within the cache.
+func (c *CredentialCacheHMAC) Valid(username, password string) (valid, ok bool) {
+ c.mu.Lock()
+
+ defer c.mu.Unlock()
+
+ var (
+ entry CachedCredential
+ err error
+ )
+
+ if entry, ok = c.values[username]; ok {
+ if entry.expires.Before(time.Now()) {
+ delete(c.values, username)
+
+ return false, false
+ }
+ }
+
+ var value []byte
+
+ if value, err = c.sum(username, password); err != nil {
+ return false, false
+ }
+
+ valid = hmac.Equal(value, entry.value)
+
+ c.hash.Reset()
+
+ return valid, true
+}
+
+func (c *CredentialCacheHMAC) sum(username, password string) (sum []byte, err error) {
+ defer c.hash.Reset()
+
+ if _, err = c.hash.Write([]byte(password)); err != nil {
+ return nil, fmt.Errorf("error occurred calculating cache hmac: %w", err)
+ }
+
+ if _, err = c.hash.Write([]byte(username)); err != nil {
+ return nil, fmt.Errorf("error occurred calculating cache hmac: %w", err)
+ }
+
+ return c.hash.Sum(nil), nil
+}
+
+// Put a new credential combination into the cache.
+func (c *CredentialCacheHMAC) Put(username, password string) (err error) {
+ c.mu.Lock()
+
+ defer c.mu.Unlock()
+
+ var value []byte
+
+ if value, err = c.sum(username, password); err != nil {
+ return err
+ }
+
+ c.values[username] = CachedCredential{expires: time.Now().Add(c.lifespan), value: value}
+
+ return nil
+}
+
+// CachedCredential is a cached credential which has an expiration and checksum value.
+type CachedCredential struct {
+ expires time.Time
+ value []byte
+}
diff --git a/internal/authentication/cached_test.go b/internal/authentication/cached_test.go
new file mode 100644
index 000000000..a60fc3b99
--- /dev/null
+++ b/internal/authentication/cached_test.go
@@ -0,0 +1,35 @@
+package authentication
+
+import (
+ "crypto/sha256"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestNewCredentialCacheHMAC(t *testing.T) {
+ cache := NewCredentialCacheHMAC(sha256.New, time.Second*2)
+
+ require.NoError(t, cache.Put("abc", "123"))
+
+ var valid, found bool
+
+ valid, found = cache.Valid("abc", "123")
+
+ assert.True(t, found)
+ assert.True(t, valid)
+
+ valid, found = cache.Valid("abc", "123")
+
+ assert.True(t, found)
+ assert.True(t, valid)
+
+ time.Sleep(time.Second * 2)
+
+ valid, found = cache.Valid("abc", "123")
+
+ assert.False(t, found)
+ assert.False(t, valid)
+}
diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go
index 3bc9084b4..5c38f093c 100644
--- a/internal/configuration/schema/keys.go
+++ b/internal/configuration/schema/keys.go
@@ -327,6 +327,7 @@ var Keys = []string{
"server.endpoints.authz.*",
"server.endpoints.authz.*.authn_strategies",
"server.endpoints.authz.*.authn_strategies[].name",
+ "server.endpoints.authz.*.authn_strategies[].scheme_basic_cache_lifespan",
"server.endpoints.authz.*.authn_strategies[].schemes",
"server.endpoints.authz.*.implementation",
"server.endpoints.enable_expvars",
diff --git a/internal/configuration/schema/server.go b/internal/configuration/schema/server.go
index 5347b41d0..7ab781c63 100644
--- a/internal/configuration/schema/server.go
+++ b/internal/configuration/schema/server.go
@@ -36,8 +36,9 @@ type ServerEndpointsAuthz struct {
// ServerEndpointsAuthzAuthnStrategy is the Authz endpoints configuration for the HTTP server.
type ServerEndpointsAuthzAuthnStrategy struct {
- Name string `koanf:"name" json:"name" jsonschema:"enum=HeaderAuthorization,enum=HeaderProxyAuthorization,enum=HeaderAuthRequestProxyAuthorization,enum=HeaderLegacy,enum=CookieSession,title=Name" jsonschema_description:"The name of the Authorization strategy to use."`
- Schemes []string `koanf:"schemes" json:"schemes" jsonschema:"enum=basic,enum=bearer,default=basic,title=Authorization Schemes" jsonschema_description:"The name of the authorization schemes to allow with the header strategies."`
+ Name string `koanf:"name" json:"name" jsonschema:"enum=HeaderAuthorization,enum=HeaderProxyAuthorization,enum=HeaderAuthRequestProxyAuthorization,enum=HeaderLegacy,enum=CookieSession,title=Name" jsonschema_description:"The name of the Authorization strategy to use."`
+ Schemes []string `koanf:"schemes" json:"schemes" jsonschema:"enum=basic,enum=bearer,default=basic,title=Authorization Schemes" jsonschema_description:"The name of the authorization schemes to allow with the header strategies."`
+ SchemeBasicCacheLifespan time.Duration `koanf:"scheme_basic_cache_lifespan" json:"scheme_basic_cache_lifespan" jsonschema:"default=0,title=Scheme Basic Cache Lifespan" jsonschema_description:"The lifespan for cached basic scheme authorization attempts."`
}
// ServerTLS represents the configuration of the http servers TLS options.
diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go
index 37f04308d..588c9d30b 100644
--- a/internal/configuration/validator/const.go
+++ b/internal/configuration/validator/const.go
@@ -424,6 +424,7 @@ const (
errFmtServerEndpointsAuthzSchemes = "server: endpoints: authz: %s: authn_strategies: strategy #%d (%s): option 'schemes' must only include the values %s but has '%s'"
errFmtServerEndpointsAuthzSchemesInvalidForStrategy = "server: endpoints: authz: %s: authn_strategies: strategy #%d (%s): option 'schemes' is not valid for the strategy"
errFmtServerEndpointsAuthzStrategyNoName = "server: endpoints: authz: %s: authn_strategies: strategy #%d: option 'name' must be configured"
+ errFmtServerEndpointsAuthzStrategySchemeOnlyOption = "server: endpoints: authz: %s: authn_strategies: strategy #%d: option '%s' can't be configured unless the '%s' scheme is configured but only the %s schemes are configured"
errFmtServerEndpointsAuthzStrategyDuplicate = "server: endpoints: authz: %s: authn_strategies: duplicate strategy name detected with name '%s'"
errFmtServerEndpointsAuthzPrefixDuplicate = "server: endpoints: authz: %s: endpoint starts with the same prefix as the '%s' endpoint with the '%s' implementation which accepts prefixes as part of its implementation"
errFmtServerEndpointsAuthzInvalidName = "server: endpoints: authz: %s: contains invalid characters"
diff --git a/internal/configuration/validator/server.go b/internal/configuration/validator/server.go
index 4a5ba4b38..95589ab1b 100644
--- a/internal/configuration/validator/server.go
+++ b/internal/configuration/validator/server.go
@@ -213,6 +213,10 @@ func validateServerEndpointsAuthzStrategies(name, implementation string, strateg
names = append(names, strategy.Name)
+ if strategy.SchemeBasicCacheLifespan > 0 && !utils.IsStringInSlice(schema.SchemeBasic, strategy.Schemes) {
+ validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzStrategySchemeOnlyOption, name, i+1, "scheme_basic_cache_lifespan", schema.SchemeBasic, utils.StringJoinAnd(strategy.Schemes)))
+ }
+
switch {
case strategy.Name == "":
validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzStrategyNoName, name, i+1))
diff --git a/internal/configuration/validator/server_test.go b/internal/configuration/validator/server_test.go
index c4e7b6e62..24c080baa 100644
--- a/internal/configuration/validator/server_test.go
+++ b/internal/configuration/validator/server_test.go
@@ -430,6 +430,15 @@ func TestServerAuthzEndpointErrors(t *testing.T) {
},
},
{
+ "ShouldErrorOnInvalidSchemeOption",
+ map[string]schema.ServerEndpointsAuthz{
+ "example": {Implementation: "ForwardAuth", AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{{Name: "HeaderAuthorization", SchemeBasicCacheLifespan: time.Minute, Schemes: []string{"bearer"}}}},
+ },
+ []string{
+ "server: endpoints: authz: example: authn_strategies: strategy #1: option 'scheme_basic_cache_lifespan' can't be configured unless the 'basic' scheme is configured but only the 'bearer' schemes are configured",
+ },
+ },
+ {
"ShouldErrorOnInvalidChars",
map[string]schema.ServerEndpointsAuthz{
"/abc": {Implementation: "ForwardAuth"},
diff --git a/internal/handlers/handler_authz_authn.go b/internal/handlers/handler_authz_authn.go
index 66a5b62c5..d3fb432ed 100644
--- a/internal/handlers/handler_authz_authn.go
+++ b/internal/handlers/handler_authz_authn.go
@@ -3,6 +3,7 @@ package handlers
import (
"bytes"
"context"
+ "crypto/sha256"
"encoding/base64"
"errors"
"fmt"
@@ -34,7 +35,7 @@ func NewCookieSessionAuthnStrategy(refresh schema.RefreshIntervalDuration) *Cook
// NewHeaderAuthorizationAuthnStrategy creates a new HeaderAuthnStrategy using the Authorization and WWW-Authenticate
// headers, and the 407 Proxy Auth Required response.
-func NewHeaderAuthorizationAuthnStrategy(schemes ...string) *HeaderAuthnStrategy {
+func NewHeaderAuthorizationAuthnStrategy(schemaBasicCacheLifeSpan time.Duration, schemes ...string) *HeaderAuthnStrategy {
return &HeaderAuthnStrategy{
authn: AuthnTypeAuthorization,
headerAuthorize: headerAuthorization,
@@ -42,12 +43,13 @@ func NewHeaderAuthorizationAuthnStrategy(schemes ...string) *HeaderAuthnStrategy
handleAuthenticate: true,
statusAuthenticate: fasthttp.StatusUnauthorized,
schemes: model.NewAuthorizationSchemes(schemes...),
+ basic: NewBasicAuthHandler(schemaBasicCacheLifeSpan),
}
}
// NewHeaderProxyAuthorizationAuthnStrategy creates a new HeaderAuthnStrategy using the Proxy-Authorization and
// Proxy-Authenticate headers, and the 407 Proxy Auth Required response.
-func NewHeaderProxyAuthorizationAuthnStrategy(schemes ...string) *HeaderAuthnStrategy {
+func NewHeaderProxyAuthorizationAuthnStrategy(schemaBasicCacheLifeSpan time.Duration, schemes ...string) *HeaderAuthnStrategy {
return &HeaderAuthnStrategy{
authn: AuthnTypeProxyAuthorization,
headerAuthorize: headerProxyAuthorization,
@@ -55,13 +57,14 @@ func NewHeaderProxyAuthorizationAuthnStrategy(schemes ...string) *HeaderAuthnStr
handleAuthenticate: true,
statusAuthenticate: fasthttp.StatusProxyAuthRequired,
schemes: model.NewAuthorizationSchemes(schemes...),
+ basic: NewBasicAuthHandler(schemaBasicCacheLifeSpan),
}
}
// NewHeaderProxyAuthorizationAuthRequestAuthnStrategy creates a new HeaderAuthnStrategy using the Proxy-Authorization
// and WWW-Authenticate headers, and the 401 Proxy Auth Required response. This is a special AuthnStrategy for the
// AuthRequest implementation.
-func NewHeaderProxyAuthorizationAuthRequestAuthnStrategy(schemes ...string) *HeaderAuthnStrategy {
+func NewHeaderProxyAuthorizationAuthRequestAuthnStrategy(schemaBasicCacheLifeSpan time.Duration, schemes ...string) *HeaderAuthnStrategy {
return &HeaderAuthnStrategy{
authn: AuthnTypeProxyAuthorization,
headerAuthorize: headerProxyAuthorization,
@@ -69,6 +72,7 @@ func NewHeaderProxyAuthorizationAuthRequestAuthnStrategy(schemes ...string) *Hea
handleAuthenticate: true,
statusAuthenticate: fasthttp.StatusUnauthorized,
schemes: model.NewAuthorizationSchemes(schemes...),
+ basic: NewBasicAuthHandler(schemaBasicCacheLifeSpan),
}
}
@@ -164,6 +168,53 @@ type HeaderAuthnStrategy struct {
handleAuthenticate bool
statusAuthenticate int
schemes model.AuthorizationSchemes
+
+ basic BasicAuthHandler
+}
+
+// BasicAuthHandler is a function signature that handles basic authentication. This is used to implement caching.
+type BasicAuthHandler func(ctx *middlewares.AutheliaCtx, authorization *model.Authorization) (valid, cached bool, err error)
+
+// NewBasicAuthHandler creates a new BasicAuthHandler depending on the lifespan.
+func NewBasicAuthHandler(lifespan time.Duration) BasicAuthHandler {
+ if lifespan == 0 {
+ return DefaultBasicAuthHandler
+ }
+
+ return NewCachedBasicAuthHandler(lifespan)
+}
+
+// DefaultBasicAuthHandler is a BasicAuthHandler that just checks the username and password directly.
+func DefaultBasicAuthHandler(ctx *middlewares.AutheliaCtx, authorization *model.Authorization) (valid, cached bool, err error) {
+ valid, err = ctx.Providers.UserProvider.CheckUserPassword(authorization.Basic())
+
+ return valid, false, err
+}
+
+// NewCachedBasicAuthHandler creates a new BasicAuthHandler which uses the authentication.NewCredentialCacheHMAC using
+// the sha256 checksum functions.
+func NewCachedBasicAuthHandler(lifespan time.Duration) BasicAuthHandler {
+ cache := authentication.NewCredentialCacheHMAC(sha256.New, lifespan)
+
+ return func(ctx *middlewares.AutheliaCtx, authorization *model.Authorization) (valid, cached bool, err error) {
+ if valid, _ = cache.Valid(authorization.Basic()); valid {
+ return true, true, nil
+ }
+
+ if valid, err = ctx.Providers.UserProvider.CheckUserPassword(authorization.Basic()); err != nil {
+ return false, false, err
+ }
+
+ if valid {
+ if err = cache.Put(authorization.Basic()); err != nil {
+ ctx.Logger.WithError(err).Errorf("Error occurred saving basic authorization credentials to cache for user '%s'", authorization.BasicUsername())
+ }
+
+ return true, false, nil
+ }
+
+ return false, false, nil
+ }
}
// Get returns the Authn information for this AuthnStrategy.
@@ -259,34 +310,31 @@ func (s *HeaderAuthnStrategy) Get(ctx *middlewares.AutheliaCtx, _ *session.Sessi
func (s *HeaderAuthnStrategy) handleGetBasic(ctx *middlewares.AutheliaCtx, authn *Authn, object *authorization.Object) (username string, level authentication.Level, err error) {
var (
- valid bool
- password string
- )
-
- username, password = authn.Header.Authorization.Basic()
-
- if valid, err = ctx.Providers.UserProvider.CheckUserPassword(username, password); err != nil {
- doMarkAuthenticationAttemptWithRequest(ctx, false, regulation.NewBan(regulation.BanTypeNone, username, nil), regulation.AuthType1FA, object.String(), object.Method, err)
-
- return "", authentication.NotAuthenticated, fmt.Errorf("failed to validate parsed credentials of %s header for user '%s': %w", s.headerAuthorize, username, err)
- }
-
- var (
ban regulation.BanType
value string
expires *time.Time
)
+ username = authn.Header.Authorization.BasicUsername()
+
if ban, value, expires, err = ctx.Providers.Regulator.BanCheck(ctx, username); err != nil {
if errors.Is(err, regulation.ErrUserIsBanned) {
doMarkAuthenticationAttemptWithRequest(ctx, false, regulation.NewBan(ban, value, expires), regulation.AuthType1FA, object.String(), object.Method, nil)
- return "", authentication.NotAuthenticated, fmt.Errorf("validated parsed credentials of %s header for user '%s' but they are currently banned: %w", s.headerAuthorize, username, err)
+ return "", authentication.NotAuthenticated, fmt.Errorf("failed to validate the credentials of user '%s' parsed from the %s header: %w", username, s.headerAuthorize, err)
}
ctx.Logger.WithError(err).Errorf(logFmtErrRegulationFail, regulation.AuthType1FA, username)
- return "", authentication.NotAuthenticated, fmt.Errorf("validated parsed credentials of %s header for user '%s' but an error occurred checking the regulation status of the user: %w", s.headerAuthorize, username, err)
+ return "", authentication.NotAuthenticated, fmt.Errorf("failed to check the regulation status of user '%s' during an attempt to authenticate using the %s header: %w", username, s.headerAuthorize, err)
+ }
+
+ var valid, cached bool
+
+ if valid, cached, err = s.basic(ctx, authn.Header.Authorization); err != nil {
+ doMarkAuthenticationAttemptWithRequest(ctx, false, regulation.NewBan(regulation.BanTypeNone, username, nil), regulation.AuthType1FA, object.String(), object.Method, err)
+
+ return "", authentication.NotAuthenticated, fmt.Errorf("failed to validate the credentials of user '%s' parsed from the %s header: %w", username, s.headerAuthorize, err)
}
if !valid {
@@ -295,13 +343,11 @@ func (s *HeaderAuthnStrategy) handleGetBasic(ctx *middlewares.AutheliaCtx, authn
return "", authentication.NotAuthenticated, fmt.Errorf("failed to validate parsed credentials of %s header valid for user '%s': the username and password do not match", s.headerAuthorize, username)
}
- doMarkAuthenticationAttemptWithRequest(ctx, true, regulation.NewBan(regulation.BanTypeNone, username, nil), regulation.AuthType1FA, object.String(), object.Method, nil)
-
- if !valid {
- return "", authentication.NotAuthenticated, fmt.Errorf("validated parsed credentials of %s header but they are not valid for user '%s': %w", s.headerAuthorize, authn.Header.Authorization.BasicUsername(), err)
+ if !cached {
+ doMarkAuthenticationAttemptWithRequest(ctx, true, regulation.NewBan(regulation.BanTypeNone, username, nil), regulation.AuthType1FA, object.String(), object.Method, nil)
}
- return authn.Header.Authorization.BasicUsername(), authentication.OneFactor, nil
+ return username, authentication.OneFactor, nil
}
// CanHandleUnauthorized returns true if this AuthnStrategy should handle Unauthorized requests.
diff --git a/internal/handlers/handler_authz_builder.go b/internal/handlers/handler_authz_builder.go
index c82b52c3c..003043c78 100644
--- a/internal/handlers/handler_authz_builder.go
+++ b/internal/handlers/handler_authz_builder.go
@@ -1,6 +1,8 @@
package handlers
import (
+ "time"
+
"github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/configuration/schema"
@@ -88,11 +90,11 @@ func (b *AuthzBuilder) WithEndpointConfig(config schema.ServerEndpointsAuthz) *A
case AuthnStrategyCookieSession:
b.strategies = append(b.strategies, NewCookieSessionAuthnStrategy(b.config.RefreshInterval))
case AuthnStrategyHeaderAuthorization:
- b.strategies = append(b.strategies, NewHeaderAuthorizationAuthnStrategy(strategy.Schemes...))
+ b.strategies = append(b.strategies, NewHeaderAuthorizationAuthnStrategy(strategy.SchemeBasicCacheLifespan, strategy.Schemes...))
case AuthnStrategyHeaderProxyAuthorization:
- b.strategies = append(b.strategies, NewHeaderProxyAuthorizationAuthnStrategy(strategy.Schemes...))
+ b.strategies = append(b.strategies, NewHeaderProxyAuthorizationAuthnStrategy(strategy.SchemeBasicCacheLifespan, strategy.Schemes...))
case AuthnStrategyHeaderAuthRequestProxyAuthorization:
- b.strategies = append(b.strategies, NewHeaderProxyAuthorizationAuthRequestAuthnStrategy(strategy.Schemes...))
+ b.strategies = append(b.strategies, NewHeaderProxyAuthorizationAuthRequestAuthnStrategy(strategy.SchemeBasicCacheLifespan, strategy.Schemes...))
case AuthnStrategyHeaderLegacy:
b.strategies = append(b.strategies, NewHeaderLegacyAuthnStrategy())
}
@@ -117,9 +119,9 @@ func (b *AuthzBuilder) Build() (authz *Authz) {
case AuthzImplLegacy:
authz.strategies = []AuthnStrategy{NewHeaderLegacyAuthnStrategy(), NewCookieSessionAuthnStrategy(b.config.RefreshInterval)}
case AuthzImplAuthRequest:
- authz.strategies = []AuthnStrategy{NewHeaderProxyAuthorizationAuthRequestAuthnStrategy(model.AuthorizationSchemeBasic.String()), NewCookieSessionAuthnStrategy(b.config.RefreshInterval)}
+ authz.strategies = []AuthnStrategy{NewHeaderProxyAuthorizationAuthRequestAuthnStrategy(time.Duration(0), model.AuthorizationSchemeBasic.String()), NewCookieSessionAuthnStrategy(b.config.RefreshInterval)}
default:
- authz.strategies = []AuthnStrategy{NewHeaderProxyAuthorizationAuthnStrategy(model.AuthorizationSchemeBasic.String()), NewCookieSessionAuthnStrategy(b.config.RefreshInterval)}
+ authz.strategies = []AuthnStrategy{NewHeaderProxyAuthorizationAuthnStrategy(time.Duration(0), model.AuthorizationSchemeBasic.String()), NewCookieSessionAuthnStrategy(b.config.RefreshInterval)}
}
}
diff --git a/internal/handlers/handler_authz_builder_test.go b/internal/handlers/handler_authz_builder_test.go
index a2d6609e0..fb3e1403c 100644
--- a/internal/handlers/handler_authz_builder_test.go
+++ b/internal/handlers/handler_authz_builder_test.go
@@ -71,7 +71,7 @@ func TestAuthzBuilder_WithEndpointConfig(t *testing.T) {
builder.WithEndpointConfig(schema.ServerEndpointsAuthz{
Implementation: "ExtAuthz",
AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{
- {Name: "HeaderProxyAuthorization"},
+ {Name: "HeaderProxyAuthorization", SchemeBasicCacheLifespan: time.Hour},
{Name: "CookieSession"},
},
})
@@ -83,7 +83,7 @@ func TestAuthzBuilder_WithEndpointConfig(t *testing.T) {
AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{
{Name: "HeaderAuthorization"},
{Name: "HeaderProxyAuthorization"},
- {Name: "HeaderAuthRequestProxyAuthorization"},
+ {Name: "HeaderAuthRequestProxyAuthorization", SchemeBasicCacheLifespan: time.Hour},
{Name: "HeaderLegacy"},
{Name: "CookieSession"},
},
diff --git a/internal/handlers/handler_authz_test.go b/internal/handlers/handler_authz_test.go
index e3161cb90..27400db87 100644
--- a/internal/handlers/handler_authz_test.go
+++ b/internal/handlers/handler_authz_test.go
@@ -84,11 +84,28 @@ func (s *AuthzSuite) Builder() (builder *AuthzBuilder) {
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()))
+ return NewAuthzBuilder().WithImplementationExtAuthz().WithStrategies(NewHeaderProxyAuthorizationAuthnStrategy(time.Duration(0), 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()))
+ return NewAuthzBuilder().WithImplementationForwardAuth().WithStrategies(NewHeaderProxyAuthorizationAuthnStrategy(time.Duration(0), 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()))
+ return NewAuthzBuilder().WithImplementationAuthRequest().WithStrategies(NewHeaderProxyAuthorizationAuthRequestAuthnStrategy(time.Duration(0), 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) BuilderWithProxyAuthorizationBasicSchemeCached() (builder *AuthzBuilder) {
+ switch s.implementation {
+ case AuthzImplExtAuthz:
+ return NewAuthzBuilder().WithImplementationExtAuthz().WithStrategies(NewHeaderProxyAuthorizationAuthnStrategy(time.Minute, model.AuthorizationSchemeBasic.String()), NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDurationAlways()))
+ case AuthzImplForwardAuth:
+ return NewAuthzBuilder().WithImplementationForwardAuth().WithStrategies(NewHeaderProxyAuthorizationAuthnStrategy(time.Minute, model.AuthorizationSchemeBasic.String()), NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDurationAlways()))
+ case AuthzImplAuthRequest:
+ return NewAuthzBuilder().WithImplementationAuthRequest().WithStrategies(NewHeaderProxyAuthorizationAuthRequestAuthnStrategy(time.Minute, model.AuthorizationSchemeBasic.String()), NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDurationAlways()))
case AuthzImplLegacy:
return NewAuthzBuilder().WithImplementationLegacy().WithStrategies(NewHeaderLegacyAuthnStrategy(), NewCookieSessionAuthnStrategy(schema.NewRefreshIntervalDurationAlways()))
default:
@@ -378,6 +395,348 @@ func (s *AuthzSuite) TestShouldVerifyFailureToGetDetailsUsingBasicScheme() {
}
}
+func (s *AuthzSuite) TestShouldVerifyFailureToGetDetailsUsingBasicSchemeCached() {
+ if s.setRequest == nil {
+ s.T().Skip()
+ }
+
+ authz := s.BuilderWithProxyAuthorizationBasicSchemeCached().Build()
+
+ mock := mocks.NewMockAutheliaCtx(s.T())
+
+ defer mock.Close()
+
+ setUpMockClock(mock)
+
+ 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==")
+
+ attempt := model.AuthenticationAttempt{
+ Time: mock.Ctx.Clock.Now(),
+ Successful: true,
+ Banned: false,
+ Username: "john",
+ Type: regulation.AuthType1FA,
+ RemoteIP: model.NewNullIP(mock.Ctx.RemoteIP()),
+ RequestURI: "https://one-factor.example.com",
+ RequestMethod: fasthttp.MethodGet,
+ }
+
+ if s.implementation == AuthzImplLegacy {
+ 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")),
+ )
+ } else {
+ gomock.InOrder(
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedIP(gomock.Eq(mock.Ctx), gomock.Eq(model.NewIP(mock.Ctx.RemoteIP()))).Return(nil, nil),
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedUser(gomock.Eq(mock.Ctx), gomock.Eq("john")).Return(nil, nil),
+ mock.UserProviderMock.EXPECT().
+ CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
+ Return(true, nil),
+ mock.StorageMock.
+ EXPECT().
+ AppendAuthenticationLog(gomock.Eq(mock.Ctx), gomock.Eq(attempt)).Return(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)))
+ }
+
+ mock.Ctx.Request.Reset()
+ mock.Ctx.Response.Reset()
+
+ s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
+
+ mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Basic am9objpwYXNzd29yZA==")
+
+ if s.implementation == AuthzImplLegacy {
+ 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")),
+ )
+ } else {
+ gomock.InOrder(
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedIP(gomock.Eq(mock.Ctx), gomock.Eq(model.NewIP(mock.Ctx.RemoteIP()))).Return(nil, nil),
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedUser(gomock.Eq(mock.Ctx), gomock.Eq("john")).Return(nil, 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) TestShouldVerifyFailureToCheckPasswordUsingBasicSchemeCached() {
+ if s.setRequest == nil {
+ s.T().Skip()
+ }
+
+ authz := s.BuilderWithProxyAuthorizationBasicSchemeCached().Build()
+
+ mock := mocks.NewMockAutheliaCtx(s.T())
+
+ defer mock.Close()
+
+ setUpMockClock(mock)
+
+ 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==")
+
+ attempt := model.AuthenticationAttempt{
+ Time: mock.Ctx.Clock.Now(),
+ Successful: false,
+ Banned: false,
+ Username: "john",
+ Type: regulation.AuthType1FA,
+ RemoteIP: model.NewNullIP(mock.Ctx.RemoteIP()),
+ RequestURI: "https://one-factor.example.com",
+ RequestMethod: fasthttp.MethodGet,
+ }
+
+ if s.implementation == AuthzImplLegacy {
+ gomock.InOrder(
+ mock.UserProviderMock.EXPECT().
+ CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
+ Return(false, nil),
+ )
+ } else {
+ gomock.InOrder(
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedIP(gomock.Eq(mock.Ctx), gomock.Eq(model.NewIP(mock.Ctx.RemoteIP()))).Return(nil, nil),
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedUser(gomock.Eq(mock.Ctx), gomock.Eq("john")).Return(nil, nil),
+ mock.UserProviderMock.EXPECT().
+ CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
+ Return(false, nil),
+ mock.StorageMock.
+ EXPECT().
+ AppendAuthenticationLog(gomock.Eq(mock.Ctx), gomock.Eq(attempt)).Return(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)))
+ }
+
+ mock.Ctx.Request.Reset()
+ mock.Ctx.Response.Reset()
+
+ s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
+
+ mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Basic am9objpwYXNzd29yZA==")
+
+ if s.implementation == AuthzImplLegacy {
+ gomock.InOrder(
+ mock.UserProviderMock.EXPECT().
+ CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
+ Return(false, nil),
+ )
+ } else {
+ gomock.InOrder(
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedIP(gomock.Eq(mock.Ctx), gomock.Eq(model.NewIP(mock.Ctx.RemoteIP()))).Return(nil, nil),
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedUser(gomock.Eq(mock.Ctx), gomock.Eq("john")).Return(nil, nil),
+ mock.UserProviderMock.EXPECT().
+ CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
+ Return(false, nil),
+ mock.StorageMock.
+ EXPECT().
+ AppendAuthenticationLog(gomock.Eq(mock.Ctx), gomock.Eq(attempt)).Return(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) TestShouldVerifyErrorToCheckPasswordUsingBasicSchemeCached() {
+ if s.setRequest == nil {
+ s.T().Skip()
+ }
+
+ authz := s.BuilderWithProxyAuthorizationBasicSchemeCached().Build()
+
+ mock := mocks.NewMockAutheliaCtx(s.T())
+
+ defer mock.Close()
+
+ setUpMockClock(mock)
+
+ 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==")
+
+ attempt := model.AuthenticationAttempt{
+ Time: mock.Ctx.Clock.Now(),
+ Successful: false,
+ Banned: false,
+ Username: "john",
+ Type: regulation.AuthType1FA,
+ RemoteIP: model.NewNullIP(mock.Ctx.RemoteIP()),
+ RequestURI: "https://one-factor.example.com",
+ RequestMethod: fasthttp.MethodGet,
+ }
+
+ if s.implementation == AuthzImplLegacy {
+ gomock.InOrder(
+ mock.UserProviderMock.EXPECT().
+ CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
+ Return(false, fmt.Errorf("bad data")),
+ )
+ } else {
+ gomock.InOrder(
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedIP(gomock.Eq(mock.Ctx), gomock.Eq(model.NewIP(mock.Ctx.RemoteIP()))).Return(nil, nil),
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedUser(gomock.Eq(mock.Ctx), gomock.Eq("john")).Return(nil, nil),
+ mock.UserProviderMock.EXPECT().
+ CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
+ Return(false, fmt.Errorf("bad data")),
+ mock.StorageMock.
+ EXPECT().
+ AppendAuthenticationLog(gomock.Eq(mock.Ctx), gomock.Eq(attempt)).Return(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)))
+ }
+
+ mock.Ctx.Request.Reset()
+ mock.Ctx.Response.Reset()
+
+ s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
+
+ mock.Ctx.Request.Header.Set(fasthttp.HeaderProxyAuthorization, "Basic am9objpwYXNzd29yZA==")
+
+ if s.implementation == AuthzImplLegacy {
+ gomock.InOrder(
+ mock.UserProviderMock.EXPECT().
+ CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
+ Return(false, fmt.Errorf("bad data")),
+ )
+ } else {
+ gomock.InOrder(
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedIP(gomock.Eq(mock.Ctx), gomock.Eq(model.NewIP(mock.Ctx.RemoteIP()))).Return(nil, nil),
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedUser(gomock.Eq(mock.Ctx), gomock.Eq("john")).Return(nil, nil),
+ mock.UserProviderMock.EXPECT().
+ CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
+ Return(false, fmt.Errorf("bad data")),
+ mock.StorageMock.
+ EXPECT().
+ AppendAuthenticationLog(gomock.Eq(mock.Ctx), gomock.Eq(attempt)).Return(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) TestShouldVerifyBypassWithErrorToGetDetailsUsingBasicScheme() {
if s.setRequest == nil {
s.T().Skip()
@@ -654,6 +1013,114 @@ func (s *AuthzSuite) TestShouldApplyPolicyOfOneFactorDomain() {
s.Equal([]byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderProxyAuthenticate))
}
+func (s *AuthzSuite) TestShouldApplyPolicyOfOneFactorDomainCached() {
+ if s.setRequest == nil {
+ s.T().Skip()
+ }
+
+ authz := s.BuilderWithProxyAuthorizationBasicSchemeCached().Build()
+
+ mock := mocks.NewMockAutheliaCtx(s.T())
+
+ defer mock.Close()
+
+ setUpMockClock(mock)
+
+ 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==")
+
+ if s.implementation == AuthzImplLegacy {
+ gomock.InOrder(
+ 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),
+ 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),
+ )
+ } else {
+ attempt := model.AuthenticationAttempt{
+ Time: mock.Ctx.Clock.Now(),
+ Successful: true,
+ Banned: false,
+ Username: "john",
+ Type: regulation.AuthType1FA,
+ RemoteIP: model.NewNullIP(mock.Ctx.RemoteIP()),
+ RequestURI: "https://one-factor.example.com",
+ RequestMethod: fasthttp.MethodGet,
+ }
+
+ gomock.InOrder(
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedIP(gomock.Eq(mock.Ctx), gomock.Eq(model.NewIP(mock.Ctx.RemoteIP()))).Return(nil, nil),
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedUser(gomock.Eq(mock.Ctx), gomock.Eq("john")).Return(nil, nil),
+ mock.UserProviderMock.EXPECT().
+ CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
+ Return(true, nil),
+ mock.StorageMock.
+ EXPECT().
+ AppendAuthenticationLog(gomock.Eq(mock.Ctx), gomock.Eq(attempt)).Return(nil),
+ mock.UserProviderMock.EXPECT().
+ GetDetails(gomock.Eq("john")).
+ Return(&authentication.UserDetails{
+ Emails: []string{"john@example.com"},
+ Groups: []string{"dev", "admins"},
+ }, nil),
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedIP(gomock.Eq(mock.Ctx), gomock.Eq(model.NewIP(mock.Ctx.RemoteIP()))).Return(nil, nil),
+ mock.StorageMock.
+ EXPECT().
+ LoadBannedUser(gomock.Eq(mock.Ctx), gomock.Eq("john")).Return(nil, 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))
+
+ mock.Ctx.Request.Reset()
+ mock.Ctx.Response.Reset()
+
+ 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.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()
@@ -676,9 +1143,7 @@ func (s *AuthzSuite) TestShouldHandleAnyCaseSchemeParameter() {
defer mock.Close()
- mock.Ctx.Clock = &mock.Clock
-
- mock.Clock.Set(time.Now())
+ setUpMockClock(mock)
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
@@ -885,8 +1350,8 @@ func (s *AuthzSuite) TestShouldApplyPolicyOfOneFactorDomainWithAuthorizationHead
builder := NewAuthzBuilder().WithImplementationLegacy()
builder = builder.WithStrategies(
- NewHeaderAuthorizationAuthnStrategy("basic"),
- NewHeaderProxyAuthorizationAuthRequestAuthnStrategy("basic"),
+ NewHeaderAuthorizationAuthnStrategy(time.Duration(0), "basic"),
+ NewHeaderProxyAuthorizationAuthRequestAuthnStrategy(time.Duration(0), "basic"),
NewCookieSessionAuthnStrategy(builder.config.RefreshInterval),
)
@@ -967,8 +1432,8 @@ func (s *AuthzSuite) TestShouldHandleAuthzWithoutHeaderNoCookie() {
builder := NewAuthzBuilder().WithImplementationLegacy()
builder = builder.WithStrategies(
- NewHeaderAuthorizationAuthnStrategy("basic"),
- NewHeaderProxyAuthorizationAuthRequestAuthnStrategy("basic"),
+ NewHeaderAuthorizationAuthnStrategy(time.Duration(0), "basic"),
+ NewHeaderProxyAuthorizationAuthRequestAuthnStrategy(time.Duration(0), "basic"),
)
authz := builder.Build()
@@ -1000,8 +1465,8 @@ func (s *AuthzSuite) TestShouldHandleAuthzWithEmptyAuthorizationHeader() {
builder := NewAuthzBuilder().WithImplementationLegacy()
builder = builder.WithStrategies(
- NewHeaderAuthorizationAuthnStrategy("basic"),
- NewHeaderProxyAuthorizationAuthRequestAuthnStrategy("basic"),
+ NewHeaderAuthorizationAuthnStrategy(time.Duration(0), "basic"),
+ NewHeaderProxyAuthorizationAuthRequestAuthnStrategy(time.Duration(0), "basic"),
)
authz := builder.Build()
@@ -1033,8 +1498,8 @@ func (s *AuthzSuite) TestShouldHandleAuthzWithAuthorizationHeaderInvalidPassword
builder := NewAuthzBuilder().WithImplementationLegacy()
builder = builder.WithStrategies(
- NewHeaderAuthorizationAuthnStrategy("basic"),
- NewHeaderProxyAuthorizationAuthRequestAuthnStrategy("basic"),
+ NewHeaderAuthorizationAuthnStrategy(time.Duration(0), "basic"),
+ NewHeaderProxyAuthorizationAuthRequestAuthnStrategy(time.Duration(0), "basic"),
)
authz := builder.Build()
@@ -1105,7 +1570,7 @@ func (s *AuthzSuite) TestShouldHandleAuthzWithIncorrectAuthHeader() { // TestSho
builder := s.Builder()
builder = builder.WithStrategies(
- NewHeaderAuthorizationAuthnStrategy("basic"),
+ NewHeaderAuthorizationAuthnStrategy(time.Duration(0), "basic"),
)
authz := builder.Build()