mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
Rewrite and fix remaining suites in Go.
This commit is contained in:
parent
373911d199
commit
c78a732c6a
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,7 +8,6 @@ npm-debug.log*
|
|||
# Coverage reports
|
||||
coverage/
|
||||
|
||||
src/.baseDir.ts
|
||||
.vscode/
|
||||
|
||||
*.swp
|
||||
|
|
|
@ -55,10 +55,6 @@ func runCommand(cmd string, args ...string) {
|
|||
}
|
||||
}
|
||||
|
||||
func installNpmPackages() {
|
||||
runCommand("npm", "ci")
|
||||
}
|
||||
|
||||
func checkCommandExist(cmd string) {
|
||||
fmt.Print("Checking if '" + cmd + "' command is installed...")
|
||||
command := exec.Command("bash", "-c", "command -v "+cmd)
|
||||
|
@ -213,9 +209,6 @@ func Bootstrap(cobraCmd *cobra.Command, args []string) {
|
|||
log.Fatal("GOPATH is not set")
|
||||
}
|
||||
|
||||
bootstrapPrintln("Installing NPM packages for development...")
|
||||
installNpmPackages()
|
||||
|
||||
bootstrapPrintln("Building development Docker images...")
|
||||
buildHelperDockerImages()
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/clems4ever/authelia/internal/server"
|
||||
"github.com/clems4ever/authelia/internal/session"
|
||||
"github.com/clems4ever/authelia/internal/storage"
|
||||
"github.com/clems4ever/authelia/internal/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -51,12 +52,15 @@ func main() {
|
|||
|
||||
switch config.LogsLevel {
|
||||
case "info":
|
||||
logging.Logger().Info("Logging severity set to info")
|
||||
logging.SetLevel(logrus.InfoLevel)
|
||||
break
|
||||
case "debug":
|
||||
logging.Logger().Info("Logging severity set to debug")
|
||||
logging.SetLevel(logrus.DebugLevel)
|
||||
break
|
||||
case "trace":
|
||||
logging.Logger().Info("Logging severity set to trace")
|
||||
logging.SetLevel(logrus.TraceLevel)
|
||||
}
|
||||
|
||||
|
@ -90,9 +94,10 @@ func main() {
|
|||
log.Fatalf("Unrecognized notifier")
|
||||
}
|
||||
|
||||
clock := utils.RealClock{}
|
||||
authorizer := authorization.NewAuthorizer(*config.AccessControl)
|
||||
sessionProvider := session.NewProvider(config.Session)
|
||||
regulator := regulation.NewRegulator(config.Regulation, storageProvider)
|
||||
regulator := regulation.NewRegulator(config.Regulation, storageProvider, clock)
|
||||
|
||||
providers := middlewares.Providers{
|
||||
Authorizer: authorizer,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
version: '3'
|
||||
version: "3"
|
||||
services:
|
||||
authelia-backend:
|
||||
build:
|
||||
|
@ -12,7 +12,6 @@ services:
|
|||
- "${GOPATH}:/go"
|
||||
- "/tmp/authelia:/tmp/authelia"
|
||||
environment:
|
||||
- SUITE_PATH=${SUITE_PATH}
|
||||
- ENVIRONMENT=dev
|
||||
networks:
|
||||
authelianet:
|
||||
|
|
|
@ -4,4 +4,7 @@ set -x
|
|||
|
||||
go get github.com/cespare/reflex
|
||||
|
||||
mkdir -p /var/lib/authelia
|
||||
mkdir -p /etc/authelia
|
||||
|
||||
reflex -c /resources/reflex.conf
|
|
@ -27,7 +27,4 @@ retry() {
|
|||
# Build the binary
|
||||
go build -o /tmp/authelia/authelia-tmp cmd/authelia/main.go
|
||||
|
||||
# Run the temporary binary
|
||||
cd $SUITE_PATH
|
||||
|
||||
retry 3 /tmp/authelia/authelia-tmp -config ${SUITE_PATH}/configuration.yml
|
||||
retry 3 /tmp/authelia/authelia-tmp -config /etc/authelia/configuration.yml
|
|
@ -35,13 +35,15 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) {
|
|||
userPasswordOk, err := ctx.Providers.UserProvider.CheckUserPassword(bodyJSON.Username, bodyJSON.Password)
|
||||
|
||||
if err != nil {
|
||||
ctx.Logger.Debugf("Mark authentication attempt made by user %s", bodyJSON.Username)
|
||||
ctx.Providers.Regulator.Mark(bodyJSON.Username, false)
|
||||
|
||||
ctx.Error(fmt.Errorf("Error while checking password for user %s: %s", bodyJSON.Username, err.Error()), authenticationFailedMessage)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Logger.Debugf("Mark authentication attempt made by user %s", bodyJSON.Username)
|
||||
// Mark the authentication attempt and whether it was successful.
|
||||
err = ctx.Providers.Regulator.Mark(bodyJSON.Username, userPasswordOk)
|
||||
err = ctx.Providers.Regulator.Mark(bodyJSON.Username, false)
|
||||
|
||||
if err != nil {
|
||||
ctx.Error(fmt.Errorf("Unable to mark authentication: %s", err), authenticationFailedMessage)
|
||||
|
|
|
@ -3,8 +3,10 @@ package handlers
|
|||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/clems4ever/authelia/internal/mocks"
|
||||
"github.com/clems4ever/authelia/internal/models"
|
||||
|
||||
"github.com/clems4ever/authelia/internal/authentication"
|
||||
"github.com/golang/mock/gomock"
|
||||
|
@ -70,6 +72,32 @@ func (s *FirstFactorSuite) TestShouldFailIfUserProviderCheckPasswordFail() {
|
|||
s.mock.Assert200KO(s.T(), "Authentication failed. Check your credentials.")
|
||||
}
|
||||
|
||||
func (s *FirstFactorSuite) TestShouldCheckAuthenticationIsMarkedWhenInvalidCredentials() {
|
||||
t, _ := time.Parse("2006-Jan-02", "2013-Feb-03")
|
||||
s.mock.Clock.Set(t)
|
||||
|
||||
s.mock.UserProviderMock.
|
||||
EXPECT().
|
||||
CheckUserPassword(gomock.Eq("test"), gomock.Eq("hello")).
|
||||
Return(false, fmt.Errorf("Invalid credentials"))
|
||||
|
||||
s.mock.StorageProviderMock.
|
||||
EXPECT().
|
||||
AppendAuthenticationLog(gomock.Eq(models.AuthenticationAttempt{
|
||||
Username: "test",
|
||||
Successful: false,
|
||||
Time: t,
|
||||
}))
|
||||
|
||||
s.mock.Ctx.Request.SetBodyString(`{
|
||||
"username": "test",
|
||||
"password": "hello",
|
||||
"keepMeLoggedIn": true
|
||||
}`)
|
||||
|
||||
FirstFactorPost(s.mock.Ctx)
|
||||
}
|
||||
|
||||
func (s *FirstFactorSuite) TestShouldFailIfUserProviderGetDetailsFail() {
|
||||
s.mock.UserProviderMock.
|
||||
EXPECT().
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/clems4ever/authelia/internal/regulation"
|
||||
"github.com/clems4ever/authelia/internal/storage"
|
||||
|
@ -32,11 +33,34 @@ type MockAutheliaCtx struct {
|
|||
NotifierMock *MockNotifier
|
||||
|
||||
UserSession *session.UserSession
|
||||
|
||||
Clock 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
|
||||
func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
|
||||
mockAuthelia := new(MockAutheliaCtx)
|
||||
mockAuthelia.Clock = TestingClock{}
|
||||
|
||||
configuration := schema.Configuration{
|
||||
AccessControl: new(schema.AccessControlConfiguration),
|
||||
|
@ -75,7 +99,7 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
|
|||
providers.SessionProvider = session.NewProvider(
|
||||
configuration.Session)
|
||||
|
||||
providers.Regulator = regulation.NewRegulator(configuration.Regulation, providers.StorageProvider)
|
||||
providers.Regulator = regulation.NewRegulator(configuration.Regulation, providers.StorageProvider, &mockAuthelia.Clock)
|
||||
|
||||
request := &fasthttp.RequestCtx{}
|
||||
// Set a cookie to identify this client throughout the test
|
||||
|
@ -94,6 +118,7 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
|
|||
// Close close the mock
|
||||
func (m *MockAutheliaCtx) Close() {
|
||||
m.Hook.Reset()
|
||||
m.Ctrl.Finish()
|
||||
}
|
||||
|
||||
// Assert200KO assert an error response from the service.
|
||||
|
|
|
@ -7,11 +7,13 @@ import (
|
|||
"github.com/clems4ever/authelia/internal/configuration/schema"
|
||||
"github.com/clems4ever/authelia/internal/models"
|
||||
"github.com/clems4ever/authelia/internal/storage"
|
||||
"github.com/clems4ever/authelia/internal/utils"
|
||||
)
|
||||
|
||||
// NewRegulator create a regulator instance.
|
||||
func NewRegulator(configuration *schema.RegulationConfiguration, provider storage.Provider) *Regulator {
|
||||
func NewRegulator(configuration *schema.RegulationConfiguration, provider storage.Provider, clock utils.Clock) *Regulator {
|
||||
regulator := &Regulator{storageProvider: provider}
|
||||
regulator.clock = clock
|
||||
if configuration != nil {
|
||||
if configuration.FindTime > configuration.BanTime {
|
||||
panic(fmt.Errorf("find_time cannot be greater than ban_time"))
|
||||
|
@ -30,7 +32,7 @@ func (r *Regulator) Mark(username string, successful bool) error {
|
|||
return r.storageProvider.AppendAuthenticationLog(models.AuthenticationAttempt{
|
||||
Username: username,
|
||||
Successful: successful,
|
||||
Time: time.Now(),
|
||||
Time: r.clock.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -42,7 +44,7 @@ func (r *Regulator) Regulate(username string) (time.Time, error) {
|
|||
if !r.enabled {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
now := time.Now()
|
||||
now := r.clock.Now()
|
||||
|
||||
// TODO(c.michaud): make sure FindTime < BanTime.
|
||||
attempts, err := r.storageProvider.LoadLatestAuthenticationLogs(username, now.Add(-r.banTime))
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/clems4ever/authelia/internal/storage"
|
||||
"github.com/clems4ever/authelia/internal/utils"
|
||||
)
|
||||
|
||||
// Regulator an authentication regulator preventing attackers to brute force the service.
|
||||
|
@ -18,4 +19,6 @@ type Regulator struct {
|
|||
banTime time.Duration
|
||||
|
||||
storageProvider storage.Provider
|
||||
|
||||
clock utils.Clock
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ jwt_secret: unsecure_secret
|
|||
|
||||
authentication_backend:
|
||||
file:
|
||||
path: users.yml
|
||||
path: /var/lib/authelia/users.yml
|
||||
|
||||
session:
|
||||
secret: unsecure_session_secret
|
||||
|
@ -31,14 +31,13 @@ duo_api:
|
|||
access_control:
|
||||
default_policy: bypass
|
||||
rules:
|
||||
- domain: 'public.example.com'
|
||||
policy: bypass
|
||||
- domain: 'secure.example.com'
|
||||
policy: two_factor
|
||||
- domain: "public.example.com"
|
||||
policy: bypass
|
||||
- domain: "secure.example.com"
|
||||
policy: two_factor
|
||||
|
||||
notifier:
|
||||
smtp:
|
||||
host: smtp
|
||||
port: 1025
|
||||
sender: admin@example.com
|
||||
|
||||
|
|
6
internal/suites/BypassAll/docker-compose.yml
Normal file
6
internal/suites/BypassAll/docker-compose.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
version: "3"
|
||||
services:
|
||||
authelia-backend:
|
||||
volumes:
|
||||
- "./internal/suites/BypassAll/configuration.yml:/etc/authelia/configuration.yml:ro"
|
||||
- "./internal/suites/BypassAll/users.yml:/var/lib/authelia/users.yml"
|
|
@ -4,13 +4,13 @@
|
|||
|
||||
port: 9091
|
||||
|
||||
logs_level: debug
|
||||
logs_level: trace
|
||||
|
||||
jwt_secret: very_important_secret
|
||||
|
||||
authentication_backend:
|
||||
file:
|
||||
path: users.yml
|
||||
path: /var/lib/authelia/users.yml
|
||||
|
||||
session:
|
||||
secret: unsecure_session_secret
|
||||
|
@ -38,7 +38,7 @@ duo_api:
|
|||
|
||||
# Access Control
|
||||
#
|
||||
# Access control is a set of rules you can use to restrict user access to certain
|
||||
# Access control is a set of rules you can use to restrict user access to certain
|
||||
# resources.
|
||||
access_control:
|
||||
# Default policy can either be `bypass`, `one_factor`, `two_factor` or `deny`.
|
||||
|
@ -54,39 +54,38 @@ access_control:
|
|||
- domain: secure.example.com
|
||||
policy: two_factor
|
||||
|
||||
- domain: '*.example.com'
|
||||
- domain: "*.example.com"
|
||||
subject: "group:admins"
|
||||
policy: two_factor
|
||||
|
||||
- domain: dev.example.com
|
||||
resources:
|
||||
- '^/users/john/.*$'
|
||||
- "^/users/john/.*$"
|
||||
subject: "user:john"
|
||||
policy: two_factor
|
||||
|
||||
- domain: dev.example.com
|
||||
resources:
|
||||
- '^/users/harry/.*$'
|
||||
- "^/users/harry/.*$"
|
||||
subject: "user:harry"
|
||||
policy: two_factor
|
||||
|
||||
- domain: '*.mail.example.com'
|
||||
- domain: "*.mail.example.com"
|
||||
subject: "user:bob"
|
||||
policy: two_factor
|
||||
|
||||
- domain: dev.example.com
|
||||
resources:
|
||||
- '^/users/bob/.*$'
|
||||
- "^/users/bob/.*$"
|
||||
subject: "user:bob"
|
||||
policy: two_factor
|
||||
|
||||
|
||||
# Configuration of the authentication regulation mechanism.
|
||||
regulation:
|
||||
regulation:
|
||||
# Set it to 0 to disable max_retries.
|
||||
max_retries: 3
|
||||
|
||||
# The user is banned if the authenticaction failed `max_retries` times in a `find_time` seconds window.
|
||||
# The user is banned if the authenticaction failed `max_retries` times in a `find_time` seconds window.
|
||||
find_time: 300
|
||||
|
||||
# The length of time before a banned user can login again.
|
||||
|
@ -98,4 +97,3 @@ notifier:
|
|||
host: smtp
|
||||
port: 1025
|
||||
sender: admin@example.com
|
||||
|
||||
|
|
6
internal/suites/DuoPush/docker-compose.yml
Normal file
6
internal/suites/DuoPush/docker-compose.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
version: "3"
|
||||
services:
|
||||
authelia-backend:
|
||||
volumes:
|
||||
- "./internal/suites/DuoPush/configuration.yml:/etc/authelia/configuration.yml:ro"
|
||||
- "./internal/suites/DuoPush/users.yml:/var/lib/authelia/users.yml"
|
5
internal/suites/HighAvailability/docker-compose.yml
Normal file
5
internal/suites/HighAvailability/docker-compose.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
version: "3"
|
||||
services:
|
||||
authelia-backend:
|
||||
volumes:
|
||||
- "./internal/suites/HighAvailability/configuration.yml:/etc/authelia/configuration.yml:ro"
|
5
internal/suites/LDAP/docker-compose.yml
Normal file
5
internal/suites/LDAP/docker-compose.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
version: "3"
|
||||
services:
|
||||
authelia-backend:
|
||||
volumes:
|
||||
- "./internal/suites/LDAP/configuration.yml:/etc/authelia/configuration.yml:ro"
|
|
@ -12,7 +12,7 @@ jwt_secret: very_important_secret
|
|||
|
||||
authentication_backend:
|
||||
file:
|
||||
path: users.yml
|
||||
path: /var/lib/authelia/users.yml
|
||||
|
||||
session:
|
||||
secret: unsecure_session_secret
|
||||
|
|
5
internal/suites/Mariadb/docker-compose.yml
Normal file
5
internal/suites/Mariadb/docker-compose.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
version: "3"
|
||||
services:
|
||||
authelia-backend:
|
||||
volumes:
|
||||
- "./internal/suites/Mariadb/configuration.yml:/etc/authelia/configuration.yml:ro"
|
6
internal/suites/NetworkACL/docker-compose.yml
Normal file
6
internal/suites/NetworkACL/docker-compose.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
version: "3"
|
||||
services:
|
||||
authelia-backend:
|
||||
volumes:
|
||||
- "./internal/suites/NetworkACL/configuration.yml:/etc/authelia/configuration.yml:ro"
|
||||
- "./internal/suites/NetworkACL/users.yml:/var/lib/authelia/users.yml"
|
|
@ -12,7 +12,7 @@ jwt_secret: very_important_secret
|
|||
|
||||
authentication_backend:
|
||||
file:
|
||||
path: users.yml
|
||||
path: /var/lib/authelia/users.yml
|
||||
|
||||
session:
|
||||
secret: unsecure_session_secret
|
||||
|
|
6
internal/suites/Postgres/docker-compose.yml
Normal file
6
internal/suites/Postgres/docker-compose.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
version: "3"
|
||||
services:
|
||||
authelia-backend:
|
||||
volumes:
|
||||
- "./internal/suites/Postgres/configuration.yml:/etc/authelia/configuration.yml:ro"
|
||||
- "./internal/suites/Postgres/users.yml:/var/lib/authelia/users.yml"
|
|
@ -12,7 +12,7 @@ default_redirection_url: https://home.example.com:8080/
|
|||
|
||||
authentication_backend:
|
||||
file:
|
||||
path: users.yml
|
||||
path: /var/lib/authelia/users.yml
|
||||
|
||||
session:
|
||||
secret: unsecure_session_secret
|
||||
|
|
6
internal/suites/ShortTimeouts/docker-compose.yml
Normal file
6
internal/suites/ShortTimeouts/docker-compose.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
version: "3"
|
||||
services:
|
||||
authelia-backend:
|
||||
volumes:
|
||||
- "./internal/suites/ShortTimeouts/configuration.yml:/etc/authelia/configuration.yml:ro"
|
||||
- "./internal/suites/ShortTimeouts/users.yml:/var/lib/authelia/users.yml"
|
|
@ -12,7 +12,7 @@ jwt_secret: very_important_secret
|
|||
|
||||
authentication_backend:
|
||||
file:
|
||||
path: users.yml
|
||||
path: /var/lib/authelia/users.yml
|
||||
|
||||
session:
|
||||
secret: unsecure_session_secret
|
||||
|
@ -22,7 +22,7 @@ session:
|
|||
|
||||
storage:
|
||||
local:
|
||||
path: db.sqlite3
|
||||
path: /tmp/authelia/db.sqlite3
|
||||
|
||||
totp:
|
||||
issuer: example.com
|
||||
|
@ -40,37 +40,36 @@ access_control:
|
|||
- domain: secure.example.com
|
||||
policy: two_factor
|
||||
|
||||
- domain: '*.example.com'
|
||||
- domain: "*.example.com"
|
||||
subject: "group:admins"
|
||||
policy: two_factor
|
||||
|
||||
- domain: dev.example.com
|
||||
resources:
|
||||
- '^/users/john/.*$'
|
||||
- "^/users/john/.*$"
|
||||
subject: "user:john"
|
||||
policy: two_factor
|
||||
|
||||
- domain: dev.example.com
|
||||
resources:
|
||||
- '^/users/harry/.*$'
|
||||
- "^/users/harry/.*$"
|
||||
subject: "user:harry"
|
||||
policy: two_factor
|
||||
|
||||
- domain: '*.mail.example.com'
|
||||
- domain: "*.mail.example.com"
|
||||
subject: "user:bob"
|
||||
policy: two_factor
|
||||
|
||||
- domain: dev.example.com
|
||||
resources:
|
||||
- '^/users/bob/.*$'
|
||||
- "^/users/bob/.*$"
|
||||
subject: "user:bob"
|
||||
policy: two_factor
|
||||
|
||||
|
||||
regulation:
|
||||
regulation:
|
||||
# Set it to 0 to disable max_retries.
|
||||
max_retries: 3
|
||||
# The user is banned if the authenticaction failed `max_retries` times in a `find_time` seconds window.
|
||||
# The user is banned if the authenticaction failed `max_retries` times in a `find_time` seconds window.
|
||||
find_time: 300
|
||||
# The length of time before a banned user can login again.
|
||||
ban_time: 900
|
||||
|
@ -80,4 +79,3 @@ notifier:
|
|||
host: smtp
|
||||
port: 1025
|
||||
sender: admin@example.com
|
||||
|
||||
|
|
6
internal/suites/Standalone/docker-compose.yml
Normal file
6
internal/suites/Standalone/docker-compose.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
version: "3"
|
||||
services:
|
||||
authelia-backend:
|
||||
volumes:
|
||||
- "./internal/suites/Standalone/configuration.yml:/etc/authelia/configuration.yml:ro"
|
||||
- "./internal/suites/Standalone/users.yml:/var/lib/authelia/users.yml"
|
|
@ -1,28 +1,20 @@
|
|||
###############################################################
|
||||
# Users Database #
|
||||
###############################################################
|
||||
|
||||
# This file can be used if you do not have an LDAP set up.
|
||||
|
||||
users:
|
||||
john:
|
||||
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||
email: john.doe@authelia.com
|
||||
groups:
|
||||
- admins
|
||||
- dev
|
||||
|
||||
harry:
|
||||
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||
email: harry.potter@authelia.com
|
||||
groups: []
|
||||
|
||||
bob:
|
||||
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||
password: '{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/'
|
||||
email: bob.dylan@authelia.com
|
||||
groups:
|
||||
- dev
|
||||
|
||||
- dev
|
||||
harry:
|
||||
password: '{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/'
|
||||
email: harry.potter@authelia.com
|
||||
groups: []
|
||||
james:
|
||||
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||
email: james.dean@authelia.com
|
||||
password: '{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/'
|
||||
email: james.dean@authelia.com
|
||||
groups: []
|
||||
john:
|
||||
password: '{CRYPT}$6$rounds=50000$LnfgDsc2WD8F2qNf$0gcCt8jlqAGZRv2ee3mCFsfAr1P4N7kESWEf36Xtw6OjkhAcQuGVOBHXp0lFuZbppa7YlgHk3VD28aSQu9U9S1'
|
||||
email: john.doe@authelia.com
|
||||
groups:
|
||||
- admins
|
||||
- dev
|
||||
|
|
|
@ -10,7 +10,7 @@ jwt_secret: unsecure_secret
|
|||
|
||||
authentication_backend:
|
||||
file:
|
||||
path: users.yml
|
||||
path: /var/lib/authelia/users.yml
|
||||
|
||||
session:
|
||||
secret: unsecure_session_secret
|
||||
|
|
6
internal/suites/Traefik/docker-compose.yml
Normal file
6
internal/suites/Traefik/docker-compose.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
version: "3"
|
||||
services:
|
||||
authelia-backend:
|
||||
volumes:
|
||||
- "./internal/suites/Traefik/configuration.yml:/etc/authelia/configuration.yml:ro"
|
||||
- "./internal/suites/Traefik/users.yml:/var/lib/authelia/users.yml"
|
12
internal/suites/action_2fa_methods.go
Normal file
12
internal/suites/action_2fa_methods.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func (wds *WebDriverSession) doChangeMethod(ctx context.Context, t *testing.T, method string) {
|
||||
wds.WaitElementLocatedByID(ctx, t, "methods-button").Click()
|
||||
wds.WaitElementLocatedByID(ctx, t, fmt.Sprintf("%s-option", method)).Click()
|
||||
}
|
|
@ -1,24 +1,21 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func doHTTPGetQuery(s *SeleniumSuite, url string) []byte {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
func doHTTPGetQuery(t *testing.T, url string) []byte {
|
||||
client := NewHTTPClient()
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
assert.NoError(s.T(), err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req.Header.Add("Accept", "application/json")
|
||||
resp, err := client.Do(req)
|
||||
assert.NoError(s.T(), err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
|
|
@ -2,46 +2,57 @@ package suites
|
|||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func doFillLoginPageAndClick(ctx context.Context, s *SeleniumSuite, username, password string, keepMeLoggedIn bool) {
|
||||
usernameElement := WaitElementLocatedByID(ctx, s, "username")
|
||||
func (wds *WebDriverSession) doFillLoginPageAndClick(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool) {
|
||||
usernameElement := wds.WaitElementLocatedByID(ctx, t, "username-textfield")
|
||||
err := usernameElement.SendKeys(username)
|
||||
assert.NoError(s.T(), err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
passwordElement := WaitElementLocatedByID(ctx, s, "password")
|
||||
passwordElement := wds.WaitElementLocatedByID(ctx, t, "password-textfield")
|
||||
err = passwordElement.SendKeys(password)
|
||||
assert.NoError(s.T(), err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
if keepMeLoggedIn {
|
||||
keepMeLoggedInElement := WaitElementLocatedByID(ctx, s, "remember-checkbox")
|
||||
keepMeLoggedInElement := wds.WaitElementLocatedByID(ctx, t, "remember-checkbox")
|
||||
err = keepMeLoggedInElement.Click()
|
||||
assert.NoError(s.T(), err)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
buttonElement := WaitElementLocatedByTagName(ctx, s, "button")
|
||||
buttonElement := wds.WaitElementLocatedByID(ctx, t, "sign-in-button")
|
||||
err = buttonElement.Click()
|
||||
assert.NoError(s.T(), err)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func doLoginOneFactor(ctx context.Context, s *SeleniumSuite, username, password string, keepMeLoggedIn bool, targetURL string) {
|
||||
doVisitLoginPage(ctx, s, targetURL)
|
||||
doFillLoginPageAndClick(ctx, s, username, password, keepMeLoggedIn)
|
||||
// Login 1FA
|
||||
func (wds *WebDriverSession) doLoginOneFactor(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool, targetURL string) {
|
||||
wds.doVisitLoginPage(ctx, t, targetURL)
|
||||
wds.doFillLoginPageAndClick(ctx, t, username, password, keepMeLoggedIn)
|
||||
}
|
||||
|
||||
func doLoginTwoFactor(ctx context.Context, s *SeleniumSuite, username, password string, keepMeLoggedIn bool, otpSecret, targetURL string) {
|
||||
doLoginOneFactor(ctx, s, username, password, keepMeLoggedIn, targetURL)
|
||||
verifyIsSecondFactorPage(ctx, s)
|
||||
doValidateTOTP(ctx, s, otpSecret)
|
||||
// Login 1FA and 2FA subsequently (must already be registered)
|
||||
func (wds *WebDriverSession) doLoginTwoFactor(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool, otpSecret, targetURL string) {
|
||||
wds.doLoginOneFactor(ctx, t, username, password, keepMeLoggedIn, targetURL)
|
||||
wds.verifyIsSecondFactorPage(ctx, t)
|
||||
wds.doValidateTOTP(ctx, t, otpSecret)
|
||||
}
|
||||
|
||||
func doLoginAndRegisterTOTP(ctx context.Context, s *SeleniumSuite, username, password string, keepMeLoggedIn bool) string {
|
||||
doLoginOneFactor(ctx, s, username, password, keepMeLoggedIn, "")
|
||||
secret := doRegisterTOTP(ctx, s)
|
||||
s.Assert().NotNil(secret)
|
||||
doVisit(s, LoginBaseURL)
|
||||
verifyIsSecondFactorPage(ctx, s)
|
||||
// Login 1FA and register 2FA.
|
||||
func (wds *WebDriverSession) doLoginAndRegisterTOTP(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool) string {
|
||||
wds.doLoginOneFactor(ctx, t, username, password, keepMeLoggedIn, "")
|
||||
secret := wds.doRegisterTOTP(ctx, t)
|
||||
wds.doVisit(t, LoginBaseURL)
|
||||
wds.verifyIsSecondFactorPage(ctx, t)
|
||||
return secret
|
||||
}
|
||||
|
||||
// Register a user with TOTP, logout and then authenticate until TOTP-2FA.
|
||||
func (wds *WebDriverSession) doRegisterAndLogin2FA(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool, targetURL string) string {
|
||||
// Register TOTP secret and logout.
|
||||
secret := wds.doRegisterThenLogout(ctx, t, username, password)
|
||||
wds.doLoginTwoFactor(ctx, t, username, password, false, secret, targetURL)
|
||||
return secret
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package suites
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func doLogout(ctx context.Context, s *SeleniumSuite) {
|
||||
doVisit(s, "https://login.example.com:8080/#/logout")
|
||||
verifyIsFirstFactorPage(ctx, s)
|
||||
func (wds *WebDriverSession) doLogout(ctx context.Context, t *testing.T) {
|
||||
wds.doVisit(t, fmt.Sprintf("%s%s", LoginBaseURL, "/logout"))
|
||||
wds.verifyIsFirstFactorPage(ctx, t)
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ package suites
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -13,22 +13,20 @@ type message struct {
|
|||
ID int `json:"id"`
|
||||
}
|
||||
|
||||
func doGetLinkFromLastMail(s *SeleniumSuite) string {
|
||||
res := doHTTPGetQuery(s, fmt.Sprintf("%s/messages", MailBaseURL))
|
||||
func doGetLinkFromLastMail(t *testing.T) string {
|
||||
res := doHTTPGetQuery(t, fmt.Sprintf("%s/messages", MailBaseURL))
|
||||
messages := make([]message, 0)
|
||||
err := json.Unmarshal(res, &messages)
|
||||
assert.NoError(s.T(), err)
|
||||
assert.Greater(s.T(), len(messages), 0)
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, len(messages), 0)
|
||||
|
||||
messageID := messages[len(messages)-1].ID
|
||||
|
||||
res = doHTTPGetQuery(s, fmt.Sprintf("%s/messages/%d.html", MailBaseURL, messageID))
|
||||
res = doHTTPGetQuery(t, fmt.Sprintf("%s/messages/%d.html", MailBaseURL, messageID))
|
||||
|
||||
re := regexp.MustCompile(`<a href="(.+)" class="button">.*<\/a>`)
|
||||
matches := re.FindStringSubmatch(string(res))
|
||||
|
||||
if len(matches) != 2 {
|
||||
log.Fatal("Number of match for link in email is not equal to one")
|
||||
}
|
||||
assert.Len(t, matches, 2, "Number of match for link in email is not equal to one")
|
||||
return matches[1]
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package suites
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func doRegisterThenLogout(ctx context.Context, s *SeleniumSuite, username, password string) string {
|
||||
secret := doLoginAndRegisterTOTP(ctx, s, username, password, false)
|
||||
doLogout(ctx, s)
|
||||
func (wds *WebDriverSession) doRegisterThenLogout(ctx context.Context, t *testing.T, username, password string) string {
|
||||
secret := wds.doLoginAndRegisterTOTP(ctx, t, username, password, false)
|
||||
wds.doLogout(ctx, t)
|
||||
return secret
|
||||
}
|
||||
|
|
35
internal/suites/action_reset_password.go
Normal file
35
internal/suites/action_reset_password.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func (wds *WebDriverSession) doInitiatePasswordReset(ctx context.Context, t *testing.T, username string) {
|
||||
wds.WaitElementLocatedByID(ctx, t, "reset-password-button").Click()
|
||||
// Fill in username
|
||||
wds.WaitElementLocatedByID(ctx, t, "username-textfield").SendKeys(username)
|
||||
// And click on the reset button
|
||||
wds.WaitElementLocatedByID(ctx, t, "reset-button").Click()
|
||||
}
|
||||
|
||||
func (wds *WebDriverSession) doCompletePasswordReset(ctx context.Context, t *testing.T, newPassword1, newPassword2 string) {
|
||||
link := doGetLinkFromLastMail(t)
|
||||
wds.doVisit(t, link)
|
||||
|
||||
wds.WaitElementLocatedByID(ctx, t, "password1-textfield").SendKeys(newPassword1)
|
||||
wds.WaitElementLocatedByID(ctx, t, "password2-textfield").SendKeys(newPassword2)
|
||||
wds.WaitElementLocatedByID(ctx, t, "reset-button").Click()
|
||||
}
|
||||
|
||||
func (wds *WebDriverSession) doSuccessfullyCompletePasswordReset(ctx context.Context, t *testing.T, newPassword1, newPassword2 string) {
|
||||
wds.doCompletePasswordReset(ctx, t, newPassword1, newPassword2)
|
||||
wds.verifyIsFirstFactorPage(ctx, t)
|
||||
}
|
||||
|
||||
func (wds *WebDriverSession) doResetPassword(ctx context.Context, t *testing.T, username, newPassword1, newPassword2 string) {
|
||||
wds.doInitiatePasswordReset(ctx, t, username)
|
||||
// then wait for the "email sent notification"
|
||||
wds.verifyMailNotificationDisplayed(ctx, t)
|
||||
wds.doSuccessfullyCompletePasswordReset(ctx, t, newPassword1, newPassword2)
|
||||
}
|
|
@ -2,24 +2,35 @@ package suites
|
|||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func doRegisterTOTP(ctx context.Context, s *SeleniumSuite) string {
|
||||
WaitElementLocatedByClassName(ctx, s, "register-totp").Click()
|
||||
verifyBodyContains(ctx, s, "Please check your e-mails")
|
||||
link := doGetLinkFromLastMail(s)
|
||||
doVisit(s, link)
|
||||
secret, err := WaitElementLocatedByClassName(ctx, s, "base32-secret").Text()
|
||||
s.Assert().NoError(err)
|
||||
func (wds *WebDriverSession) doRegisterTOTP(ctx context.Context, t *testing.T) string {
|
||||
wds.WaitElementLocatedByID(ctx, t, "register-link").Click()
|
||||
wds.verifyMailNotificationDisplayed(ctx, t)
|
||||
link := doGetLinkFromLastMail(t)
|
||||
wds.doVisit(t, link)
|
||||
secret, err := wds.WaitElementLocatedByID(ctx, t, "base32-secret").GetAttribute("value")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, "", secret)
|
||||
assert.NotNil(t, secret)
|
||||
return secret
|
||||
}
|
||||
|
||||
func doValidateTOTP(ctx context.Context, s *SeleniumSuite, secret string) {
|
||||
code, err := totp.GenerateCode(secret, time.Now())
|
||||
s.Assert().NoError(err)
|
||||
WaitElementLocatedByID(ctx, s, "totp-token").SendKeys(code)
|
||||
WaitElementLocatedByID(ctx, s, "totp-button").Click()
|
||||
func (wds *WebDriverSession) doEnterOTP(ctx context.Context, t *testing.T, code string) {
|
||||
inputs := wds.WaitElementsLocatedByCSSSelector(ctx, t, "#otp-input input")
|
||||
|
||||
for i := 0; i < 6; i++ {
|
||||
inputs[i].SendKeys(string(code[i]))
|
||||
}
|
||||
}
|
||||
|
||||
func (wds *WebDriverSession) doValidateTOTP(ctx context.Context, t *testing.T, secret string) {
|
||||
code, err := totp.GenerateCode(secret, time.Now())
|
||||
assert.NoError(t, err)
|
||||
wds.doEnterOTP(ctx, t, code)
|
||||
}
|
||||
|
|
|
@ -3,25 +3,25 @@ package suites
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func doVisit(s *SeleniumSuite, url string) {
|
||||
err := s.WebDriver().Get(url)
|
||||
assert.NoError(s.T(), err)
|
||||
func (wds *WebDriverSession) doVisit(t *testing.T, url string) {
|
||||
err := wds.WebDriver.Get(url)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func doVisitAndVerifyURLIs(ctx context.Context, s *SeleniumSuite, url string) {
|
||||
doVisit(s, url)
|
||||
verifyURLIs(ctx, s, url)
|
||||
func (wds *WebDriverSession) doVisitAndVerifyURLIs(ctx context.Context, t *testing.T, url string) {
|
||||
wds.doVisit(t, url)
|
||||
wds.verifyURLIs(ctx, t, url)
|
||||
}
|
||||
|
||||
func doVisitLoginPage(ctx context.Context, s *SeleniumSuite, targetURL string) {
|
||||
func (wds *WebDriverSession) doVisitLoginPage(ctx context.Context, t *testing.T, targetURL string) {
|
||||
suffix := ""
|
||||
if targetURL != "" {
|
||||
suffix = fmt.Sprintf("?rd=%s", url.QueryEscape(targetURL))
|
||||
suffix = fmt.Sprintf("?rd=%s", targetURL)
|
||||
}
|
||||
doVisitAndVerifyURLIs(ctx, s, fmt.Sprintf("%s%s", LoginBaseURL, suffix))
|
||||
wds.doVisitAndVerifyURLIs(ctx, t, fmt.Sprintf("%s/%s", LoginBaseURL, suffix))
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import "fmt"
|
|||
var BaseDomain = "example.com:8080"
|
||||
|
||||
// LoginBaseURL the base URL of the login portal
|
||||
var LoginBaseURL = fmt.Sprintf("https://login.%s/", BaseDomain)
|
||||
var LoginBaseURL = fmt.Sprintf("https://login.%s", BaseDomain)
|
||||
|
||||
// SingleFactorBaseURL the base URL of the singlefactor domain
|
||||
var SingleFactorBaseURL = fmt.Sprintf("https://singlefactor.%s", BaseDomain)
|
||||
|
@ -18,4 +18,25 @@ var AdminBaseURL = fmt.Sprintf("https://admin.%s", BaseDomain)
|
|||
var MailBaseURL = fmt.Sprintf("https://mail.%s", BaseDomain)
|
||||
|
||||
// HomeBaseURL the base URL of the home domain
|
||||
var HomeBaseURL = fmt.Sprintf("https://home.%s/", BaseDomain)
|
||||
var HomeBaseURL = fmt.Sprintf("https://home.%s", BaseDomain)
|
||||
|
||||
// PublicBaseURL the base URL of the public domain
|
||||
var PublicBaseURL = fmt.Sprintf("https://public.%s", BaseDomain)
|
||||
|
||||
// SecureBaseURL the base URL of the secure domain
|
||||
var SecureBaseURL = fmt.Sprintf("https://secure.%s", BaseDomain)
|
||||
|
||||
// DevBaseURL the base URL of the dev domain
|
||||
var DevBaseURL = fmt.Sprintf("https://dev.%s", BaseDomain)
|
||||
|
||||
// MX1MailBaseURL the base URL of the mx1.mail domain
|
||||
var MX1MailBaseURL = fmt.Sprintf("https://mx1.mail.%s", BaseDomain)
|
||||
|
||||
// MX2MailBaseURL the base URL of the mx2.mail domain
|
||||
var MX2MailBaseURL = fmt.Sprintf("https://mx2.mail.%s", BaseDomain)
|
||||
|
||||
// DuoBaseURL the base URL of the Duo configuration API
|
||||
var DuoBaseURL = "https://duo.example.com"
|
||||
|
||||
// AutheliaBaseURL the base URL of Authelia service
|
||||
var AutheliaBaseURL = "http://authelia.example.com:9091"
|
||||
|
|
|
@ -2,7 +2,6 @@ package suites
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
|
@ -21,41 +20,35 @@ func NewDockerEnvironment(files []string) *DockerEnvironment {
|
|||
}
|
||||
|
||||
func (de *DockerEnvironment) createCommandWithStdout(cmd string) *exec.Cmd {
|
||||
dockerCmdLine := "docker-compose -f " + strings.Join(de.dockerComposeFiles, " -f ") + " " + cmd
|
||||
dockerCmdLine := fmt.Sprintf("docker-compose -f %s %s", strings.Join(de.dockerComposeFiles, " -f "), cmd)
|
||||
log.Trace(dockerCmdLine)
|
||||
return utils.CommandWithStdout("bash", "-c", dockerCmdLine)
|
||||
}
|
||||
|
||||
func (de *DockerEnvironment) createCommand(cmd string) *exec.Cmd {
|
||||
dockerCmdLine := "docker-compose -f " + strings.Join(de.dockerComposeFiles, " -f ") + " " + cmd
|
||||
dockerCmdLine := fmt.Sprintf("docker-compose -f %s %s", strings.Join(de.dockerComposeFiles, " -f "), cmd)
|
||||
log.Trace(dockerCmdLine)
|
||||
return exec.Command("bash", "-c", dockerCmdLine)
|
||||
return utils.Command("bash", "-c", dockerCmdLine)
|
||||
}
|
||||
|
||||
// Up spawn a docker environment
|
||||
func (de *DockerEnvironment) Up(suitePath string) error {
|
||||
cmd := de.createCommandWithStdout("up -d")
|
||||
cmd.Env = append(os.Environ(), "SUITE_PATH="+suitePath)
|
||||
return cmd.Run()
|
||||
func (de *DockerEnvironment) Up() error {
|
||||
return de.createCommandWithStdout("up -d").Run()
|
||||
}
|
||||
|
||||
// Restart restarts a service
|
||||
func (de *DockerEnvironment) Restart(suitePath, service string) error {
|
||||
cmd := de.createCommandWithStdout(fmt.Sprintf("restart %s", service))
|
||||
cmd.Env = append(os.Environ(), "SUITE_PATH="+suitePath)
|
||||
return cmd.Run()
|
||||
func (de *DockerEnvironment) Restart(service string) error {
|
||||
return de.createCommandWithStdout(fmt.Sprintf("restart %s", service)).Run()
|
||||
}
|
||||
|
||||
// Down spawn a docker environment
|
||||
func (de *DockerEnvironment) Down(suitePath string) error {
|
||||
cmd := de.createCommandWithStdout("down -v")
|
||||
cmd.Env = append(os.Environ(), "SUITE_PATH="+suitePath)
|
||||
return cmd.Run()
|
||||
func (de *DockerEnvironment) Down() error {
|
||||
return de.createCommandWithStdout("down -v").Run()
|
||||
}
|
||||
|
||||
// Logs get logs of a given service of the environment
|
||||
func (de *DockerEnvironment) Logs(service string, flags []string) (string, error) {
|
||||
cmd := de.createCommand("logs " + strings.Join(flags, " ") + " " + service)
|
||||
cmd := de.createCommand(fmt.Sprintf("logs %s %s", strings.Join(flags, " "), service))
|
||||
content, err := cmd.Output()
|
||||
return string(content), err
|
||||
}
|
||||
|
|
35
internal/suites/duo.go
Normal file
35
internal/suites/duo.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// DuoPolicy a type of policy
|
||||
type DuoPolicy int32
|
||||
|
||||
const (
|
||||
// Deny deny policy
|
||||
Deny DuoPolicy = iota
|
||||
// Allow allow policy
|
||||
Allow DuoPolicy = iota
|
||||
)
|
||||
|
||||
// ConfigureDuo configure duo api to allow or block auth requests
|
||||
func ConfigureDuo(t *testing.T, allowDeny DuoPolicy) {
|
||||
url := fmt.Sprintf("%s/allow", DuoBaseURL)
|
||||
if allowDeny == Deny {
|
||||
url = fmt.Sprintf("%s/deny", DuoBaseURL)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
client := NewHTTPClient()
|
||||
res, err := client.Do(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 200, res.StatusCode)
|
||||
}
|
21
internal/suites/http.go
Normal file
21
internal/suites/http.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewHTTPClient create a new client skipping TLS verification and not redirecting
|
||||
func NewHTTPClient() *http.Client {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
return &http.Client{
|
||||
Transport: tr,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
}
|
87
internal/suites/scenario_available_methods_test.go
Normal file
87
internal/suites/scenario_available_methods_test.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/tebeka/selenium"
|
||||
)
|
||||
|
||||
type AvailableMethodsScenario struct {
|
||||
*SeleniumSuite
|
||||
|
||||
methods []string
|
||||
}
|
||||
|
||||
func NewAvailableMethodsScenario(methods []string) *AvailableMethodsScenario {
|
||||
return &AvailableMethodsScenario{
|
||||
SeleniumSuite: new(SeleniumSuite),
|
||||
methods: methods,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AvailableMethodsScenario) SetupSuite() {
|
||||
wds, err := StartWebDriver()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.SeleniumSuite.WebDriverSession = wds
|
||||
}
|
||||
|
||||
func (s *AvailableMethodsScenario) TearDownSuite() {
|
||||
err := s.WebDriverSession.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AvailableMethodsScenario) SetupTest() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doLogout(ctx, s.T())
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
}
|
||||
|
||||
func IsStringInList(str string, list []string) bool {
|
||||
for _, v := range list {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *AvailableMethodsScenario) TestShouldCheckAvailableMethods() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
|
||||
|
||||
methodsButton := s.WaitElementLocatedByID(ctx, s.T(), "methods-button")
|
||||
err := methodsButton.Click()
|
||||
s.Assert().NoError(err)
|
||||
|
||||
methodsDialog := s.WaitElementLocatedByID(ctx, s.T(), "methods-dialog")
|
||||
options, err := methodsDialog.FindElements(selenium.ByClassName, "method-option")
|
||||
s.Assert().NoError(err)
|
||||
s.Assert().Len(options, len(s.methods))
|
||||
|
||||
optionsList := make([]string, 0)
|
||||
for _, o := range options {
|
||||
txt, err := o.Text()
|
||||
s.Assert().NoError(err)
|
||||
optionsList = append(optionsList, txt)
|
||||
}
|
||||
|
||||
s.Assert().Len(optionsList, len(s.methods))
|
||||
|
||||
for _, m := range s.methods {
|
||||
s.Assert().True(IsStringInList(m, optionsList))
|
||||
}
|
||||
}
|
57
internal/suites/scenario_backend_protection_test.go
Normal file
57
internal/suites/scenario_backend_protection_test.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type BackendProtectionScenario struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func NewBackendProtectionScenario() *BackendProtectionScenario {
|
||||
return &BackendProtectionScenario{}
|
||||
}
|
||||
|
||||
func (s *BackendProtectionScenario) AssertRequestStatusCode(method, url string, expectedStatusCode int) {
|
||||
s.Run(url, func() {
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
s.Assert().NoError(err)
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: tr,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
res, err := client.Do(req)
|
||||
s.Assert().NoError(err)
|
||||
s.Assert().Equal(res.StatusCode, expectedStatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *BackendProtectionScenario) TestProtectionOfBackendEndpoints() {
|
||||
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/totp", AutheliaBaseURL), 403)
|
||||
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/u2f/sign", AutheliaBaseURL), 403)
|
||||
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/u2f/register", AutheliaBaseURL), 403)
|
||||
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/u2f/sign_request", AutheliaBaseURL), 403)
|
||||
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/preferences", AutheliaBaseURL), 403)
|
||||
s.AssertRequestStatusCode("GET", fmt.Sprintf("%s/api/secondfactor/preferences", AutheliaBaseURL), 403)
|
||||
s.AssertRequestStatusCode("GET", fmt.Sprintf("%s/api/secondfactor/available", AutheliaBaseURL), 403)
|
||||
|
||||
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/u2f/identity/start", AutheliaBaseURL), 403)
|
||||
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/u2f/identity/finish", AutheliaBaseURL), 403)
|
||||
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/totp/identity/start", AutheliaBaseURL), 403)
|
||||
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/totp/identity/finish", AutheliaBaseURL), 403)
|
||||
}
|
||||
|
||||
func TestRunBackendProtection(t *testing.T) {
|
||||
suite.Run(t, NewBackendProtectionScenario())
|
||||
}
|
63
internal/suites/scenario_bypass_policy_test.go
Normal file
63
internal/suites/scenario_bypass_policy_test.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type BypassPolicyScenario struct {
|
||||
*SeleniumSuite
|
||||
}
|
||||
|
||||
func NewBypassPolicyScenario() *BypassPolicyScenario {
|
||||
return &BypassPolicyScenario{
|
||||
SeleniumSuite: new(SeleniumSuite),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BypassPolicyScenario) SetupSuite() {
|
||||
wds, err := StartWebDriver()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.WebDriverSession = wds
|
||||
}
|
||||
|
||||
func (s *BypassPolicyScenario) TearDownSuite() {
|
||||
err := s.WebDriverSession.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BypassPolicyScenario) SetupTest() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doLogout(ctx, s.T())
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *BypassPolicyScenario) TestShouldAccessPublicResource() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doVisit(s.T(), AdminBaseURL)
|
||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||
|
||||
s.doVisit(s.T(), fmt.Sprintf("%s/secret.html", PublicBaseURL))
|
||||
s.verifySecretAuthorized(ctx, s.T())
|
||||
}
|
||||
|
||||
func TestBypassPolicyScenario(t *testing.T) {
|
||||
suite.Run(t, NewBypassPolicyScenario())
|
||||
}
|
78
internal/suites/scenario_custom_headers_test.go
Normal file
78
internal/suites/scenario_custom_headers_test.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/tebeka/selenium"
|
||||
)
|
||||
|
||||
type CustomHeadersScenario struct {
|
||||
*SeleniumSuite
|
||||
}
|
||||
|
||||
func NewCustomHeadersScenario() *CustomHeadersScenario {
|
||||
return &CustomHeadersScenario{
|
||||
SeleniumSuite: new(SeleniumSuite),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CustomHeadersScenario) SetupSuite() {
|
||||
wds, err := StartWebDriver()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.WebDriverSession = wds
|
||||
}
|
||||
|
||||
func (s *CustomHeadersScenario) TearDownSuite() {
|
||||
err := s.WebDriverSession.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CustomHeadersScenario) SetupTest() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doLogout(ctx, s.T())
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *CustomHeadersScenario) TestShouldNotForwardCustomHeaderForUnauthenticatedUser() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doVisit(s.T(), fmt.Sprintf("%s/headers", PublicBaseURL))
|
||||
|
||||
body, err := s.WebDriver().FindElement(selenium.ByTagName, "body")
|
||||
s.Assert().NoError(err)
|
||||
s.WaitElementTextContains(ctx, s.T(), body, "httpbin:8000")
|
||||
}
|
||||
|
||||
func (s *CustomHeadersScenario) TestShouldForwardCustomHeaderForAuthenticatedUser() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
targetURL := fmt.Sprintf("%s/headers", PublicBaseURL)
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, targetURL)
|
||||
s.verifyURLIs(ctx, s.T(), targetURL)
|
||||
|
||||
body, err := s.WebDriver().FindElement(selenium.ByTagName, "body")
|
||||
s.Assert().NoError(err)
|
||||
s.WaitElementTextContains(ctx, s.T(), body, "\"Custom-Forwarded-User\": \"john\"")
|
||||
s.WaitElementTextContains(ctx, s.T(), body, "\"Custom-Forwarded-Groups\": \"admins,dev\"")
|
||||
}
|
||||
|
||||
func TestCustomHeadersScenario(t *testing.T) {
|
||||
suite.Run(t, NewCustomHeadersScenario())
|
||||
}
|
117
internal/suites/scenario_inactivity_test.go
Normal file
117
internal/suites/scenario_inactivity_test.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type InactivityScenario struct {
|
||||
*SeleniumSuite
|
||||
secret string
|
||||
}
|
||||
|
||||
func NewInactivityScenario() *InactivityScenario {
|
||||
return &InactivityScenario{
|
||||
SeleniumSuite: new(SeleniumSuite),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *InactivityScenario) SetupSuite() {
|
||||
wds, err := StartWebDriver()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.WebDriverSession = wds
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
|
||||
s.secret = s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, targetURL)
|
||||
s.verifySecretAuthorized(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *InactivityScenario) TearDownSuite() {
|
||||
err := s.WebDriverSession.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *InactivityScenario) SetupTest() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doLogout(ctx, s.T())
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *InactivityScenario) TestShouldRequireReauthenticationAfterInactivityPeriod() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
|
||||
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, s.secret, "")
|
||||
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
|
||||
time.Sleep(6 * time.Second)
|
||||
|
||||
s.doVisit(s.T(), targetURL)
|
||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *InactivityScenario) TestShouldRequireReauthenticationAfterCookieExpiration() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
|
||||
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, s.secret, "")
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
s.doVisit(s.T(), targetURL)
|
||||
s.verifySecretAuthorized(ctx, s.T())
|
||||
}
|
||||
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
s.doVisit(s.T(), targetURL)
|
||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *InactivityScenario) TestShouldDisableCookieExpirationAndInactivity() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
|
||||
s.doLoginTwoFactor(ctx, s.T(), "john", "password", true, s.secret, "")
|
||||
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
|
||||
time.Sleep(9 * time.Second)
|
||||
|
||||
s.doVisit(s.T(), targetURL)
|
||||
s.verifySecretAuthorized(ctx, s.T())
|
||||
}
|
||||
|
||||
func TestInactivityScenario(t *testing.T) {
|
||||
suite.Run(t, NewInactivityScenario())
|
||||
}
|
|
@ -14,7 +14,7 @@ type OneFactorSuite struct {
|
|||
*SeleniumSuite
|
||||
}
|
||||
|
||||
func NewOneFactorSuite() *OneFactorSuite {
|
||||
func NewOneFactorScenario() *OneFactorSuite {
|
||||
return &OneFactorSuite{
|
||||
SeleniumSuite: new(SeleniumSuite),
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ func (s *OneFactorSuite) SetupSuite() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.SeleniumSuite.WebDriverSession = wds
|
||||
s.WebDriverSession = wds
|
||||
}
|
||||
|
||||
func (s *OneFactorSuite) TearDownSuite() {
|
||||
|
@ -42,9 +42,9 @@ func (s *OneFactorSuite) SetupTest() {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
doLogout(ctx, s.SeleniumSuite)
|
||||
doVisit(s.SeleniumSuite, HomeBaseURL)
|
||||
verifyURLIs(ctx, s.SeleniumSuite, HomeBaseURL)
|
||||
s.doLogout(ctx, s.T())
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *OneFactorSuite) TestShouldAuthorizeSecretAfterOneFactor() {
|
||||
|
@ -52,8 +52,8 @@ func (s *OneFactorSuite) TestShouldAuthorizeSecretAfterOneFactor() {
|
|||
defer cancel()
|
||||
|
||||
targetURL := fmt.Sprintf("%s/secret.html", SingleFactorBaseURL)
|
||||
doLoginOneFactor(ctx, s.SeleniumSuite, "john", "password", false, targetURL)
|
||||
verifySecretAuthorized(ctx, s.SeleniumSuite)
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, targetURL)
|
||||
s.verifySecretAuthorized(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *OneFactorSuite) TestShouldRedirectToSecondFactor() {
|
||||
|
@ -61,8 +61,8 @@ func (s *OneFactorSuite) TestShouldRedirectToSecondFactor() {
|
|||
defer cancel()
|
||||
|
||||
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
|
||||
doLoginOneFactor(ctx, s.SeleniumSuite, "john", "password", false, targetURL)
|
||||
verifyIsSecondFactorPage(ctx, s.SeleniumSuite)
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, targetURL)
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *OneFactorSuite) TestShouldDenyAccessOnBadPassword() {
|
||||
|
@ -70,11 +70,11 @@ func (s *OneFactorSuite) TestShouldDenyAccessOnBadPassword() {
|
|||
defer cancel()
|
||||
|
||||
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
|
||||
doLoginOneFactor(ctx, s.SeleniumSuite, "john", "bad-password", false, targetURL)
|
||||
verifyIsFirstFactorPage(ctx, s.SeleniumSuite)
|
||||
verifyNotificationDisplayed(ctx, s.SeleniumSuite, "Authentication failed. Check your credentials.")
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "bad-password", false, targetURL)
|
||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||
s.verifyNotificationDisplayed(ctx, s.T(), "There was a problem. Username or password might be incorrect.")
|
||||
}
|
||||
|
||||
func TestRunOneFactor(t *testing.T) {
|
||||
suite.Run(t, NewOneFactorSuite())
|
||||
suite.Run(t, NewOneFactorScenario())
|
||||
}
|
||||
|
|
82
internal/suites/scenario_redirection_check_test.go
Normal file
82
internal/suites/scenario_redirection_check_test.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type RedirectionCheckScenario struct {
|
||||
*SeleniumSuite
|
||||
}
|
||||
|
||||
func NewRedirectionCheckScenario() *RedirectionCheckScenario {
|
||||
return &RedirectionCheckScenario{
|
||||
SeleniumSuite: new(SeleniumSuite),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RedirectionCheckScenario) SetupSuite() {
|
||||
wds, err := StartWebDriver()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.WebDriverSession = wds
|
||||
}
|
||||
|
||||
func (s *RedirectionCheckScenario) TearDownSuite() {
|
||||
err := s.WebDriverSession.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RedirectionCheckScenario) SetupTest() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doLogout(ctx, s.T())
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
}
|
||||
|
||||
var redirectionAuthorizations = map[string]bool{
|
||||
// external website
|
||||
"https://www.google.fr": false,
|
||||
// Not the right domain
|
||||
"https://public.example.com.a:8080/secret.html": false,
|
||||
// Not https
|
||||
"http://secure.example.com:8080/secret.html": false,
|
||||
// Domain handled by Authelia
|
||||
"https://secure.example.com:8080/secret.html": true,
|
||||
}
|
||||
|
||||
func (s *RedirectionCheckScenario) TestShouldRedirectOnlyWhenDomainIsHandledByAuthelia() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password")
|
||||
|
||||
for url, redirected := range redirectionAuthorizations {
|
||||
s.T().Run(url, func(t *testing.T) {
|
||||
s.doLoginTwoFactor(ctx, t, "john", "password", false, secret, url)
|
||||
time.Sleep(1 * time.Second)
|
||||
if redirected {
|
||||
s.verifySecretAuthorized(ctx, t)
|
||||
} else {
|
||||
s.WaitElementLocatedByClassName(ctx, t, "success-icon")
|
||||
}
|
||||
s.doLogout(ctx, t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedirectionCheckScenario(t *testing.T) {
|
||||
suite.Run(t, NewRedirectionCheckScenario())
|
||||
}
|
83
internal/suites/scenario_regulation_test.go
Normal file
83
internal/suites/scenario_regulation_test.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/tebeka/selenium"
|
||||
)
|
||||
|
||||
type RegulationScenario struct {
|
||||
*SeleniumSuite
|
||||
}
|
||||
|
||||
func NewRegulationScenario() *RegulationScenario {
|
||||
return &RegulationScenario{
|
||||
SeleniumSuite: new(SeleniumSuite),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RegulationScenario) SetupSuite() {
|
||||
wds, err := StartWebDriver()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.WebDriverSession = wds
|
||||
}
|
||||
|
||||
func (s *RegulationScenario) TearDownSuite() {
|
||||
err := s.WebDriverSession.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RegulationScenario) SetupTest() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doLogout(ctx, s.T())
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *RegulationScenario) TestShouldBanUserAfterTooManyAttempt() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doVisitLoginPage(ctx, s.T(), "")
|
||||
s.doFillLoginPageAndClick(ctx, s.T(), "john", "bad-password", false)
|
||||
s.verifyNotificationDisplayed(ctx, s.T(), "There was a problem. Username or password might be incorrect.")
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click()
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
// Reset password field
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").
|
||||
SendKeys(selenium.ControlKey + "a" + selenium.BackspaceKey)
|
||||
|
||||
// And enter the correct password
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").SendKeys("password")
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click()
|
||||
s.verifyNotificationDisplayed(ctx, s.T(), "There was a problem. Username or password might be incorrect.")
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||
|
||||
time.Sleep(9 * time.Second)
|
||||
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click()
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
}
|
||||
|
||||
func TestBlacklistingScenario(t *testing.T) {
|
||||
suite.Run(t, NewRegulationScenario())
|
||||
}
|
101
internal/suites/scenario_reset_password_test.go
Normal file
101
internal/suites/scenario_reset_password_test.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type ResetPasswordScenario struct {
|
||||
*SeleniumSuite
|
||||
}
|
||||
|
||||
func NewResetPasswordScenario() *ResetPasswordScenario {
|
||||
return &ResetPasswordScenario{SeleniumSuite: new(SeleniumSuite)}
|
||||
}
|
||||
|
||||
func (s *ResetPasswordScenario) SetupSuite() {
|
||||
wds, err := StartWebDriver()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.WebDriverSession = wds
|
||||
}
|
||||
|
||||
func (s *ResetPasswordScenario) TearDownSuite() {
|
||||
err := s.WebDriverSession.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ResetPasswordScenario) SetupTest() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doLogout(ctx, s.T())
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *ResetPasswordScenario) TestShouldResetPassword() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doVisit(s.T(), LoginBaseURL)
|
||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||
|
||||
// Reset the password to abc
|
||||
s.doResetPassword(ctx, s.T(), "john", "abc", "abc")
|
||||
|
||||
// Try to login with the old password
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
|
||||
s.verifyNotificationDisplayed(ctx, s.T(), "There was a problem. Username or password might be incorrect.")
|
||||
|
||||
// Try to login with the new password
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "abc", false, "")
|
||||
|
||||
// Logout
|
||||
s.doLogout(ctx, s.T())
|
||||
|
||||
// Reset the original password
|
||||
s.doResetPassword(ctx, s.T(), "john", "password", "password")
|
||||
}
|
||||
|
||||
func (s *ResetPasswordScenario) TestShouldMakeAttackerThinkPasswordResetIsInitiated() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doVisit(s.T(), LoginBaseURL)
|
||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||
|
||||
// Try to initiate a password reset of an inexistant user
|
||||
s.doInitiatePasswordReset(ctx, s.T(), "i_dont_exist")
|
||||
|
||||
// Check that the notification make the attacker thinks the process is initiated
|
||||
s.verifyMailNotificationDisplayed(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *ResetPasswordScenario) TestShouldLetUserNoticeThereIsAPasswordMismatch() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doVisit(s.T(), LoginBaseURL)
|
||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||
|
||||
s.doInitiatePasswordReset(ctx, s.T(), "john")
|
||||
s.verifyMailNotificationDisplayed(ctx, s.T())
|
||||
|
||||
s.doCompletePasswordReset(ctx, s.T(), "password", "another_password")
|
||||
s.verifyNotificationDisplayed(ctx, s.T(), "Passwords do not match.")
|
||||
}
|
||||
|
||||
func TestRunResetPasswordScenario(t *testing.T) {
|
||||
suite.Run(t, NewResetPasswordScenario())
|
||||
}
|
|
@ -14,7 +14,7 @@ type TwoFactorSuite struct {
|
|||
*SeleniumSuite
|
||||
}
|
||||
|
||||
func NewTwoFactorSuite() *TwoFactorSuite {
|
||||
func NewTwoFactorScenario() *TwoFactorSuite {
|
||||
return &TwoFactorSuite{
|
||||
SeleniumSuite: new(SeleniumSuite),
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ func (s *TwoFactorSuite) SetupSuite() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.SeleniumSuite.WebDriverSession = wds
|
||||
s.WebDriverSession = wds
|
||||
}
|
||||
|
||||
func (s *TwoFactorSuite) TearDownSuite() {
|
||||
|
@ -42,24 +42,44 @@ func (s *TwoFactorSuite) SetupTest() {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
doLogout(ctx, s.SeleniumSuite)
|
||||
doVisit(s.SeleniumSuite, HomeBaseURL)
|
||||
verifyURLIs(ctx, s.SeleniumSuite, HomeBaseURL)
|
||||
s.doLogout(ctx, s.T())
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *TwoFactorSuite) TestShouldAuthorizeSecretAfterTwoFactor() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Register TOTP secret and logout.
|
||||
secret := doRegisterThenLogout(ctx, s.SeleniumSuite, "john", "password")
|
||||
secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password")
|
||||
|
||||
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
|
||||
doLoginTwoFactor(ctx, s.SeleniumSuite, "john", "password", false, secret, targetURL)
|
||||
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, targetURL)
|
||||
s.verifySecretAuthorized(ctx, s.T())
|
||||
|
||||
verifySecretAuthorized(ctx, s.SeleniumSuite)
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
|
||||
s.doVisit(s.T(), targetURL)
|
||||
s.verifySecretAuthorized(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *TwoFactorSuite) TestShouldFailTwoFactor() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Register TOTP secret and logout.
|
||||
s.doRegisterThenLogout(ctx, s.T(), "john", "password")
|
||||
|
||||
wrongPasscode := "123456"
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
s.doEnterOTP(ctx, s.T(), wrongPasscode)
|
||||
|
||||
s.verifyNotificationDisplayed(ctx, s.T(), "The one-time password might be wrong")
|
||||
}
|
||||
|
||||
func TestRunTwoFactor(t *testing.T) {
|
||||
suite.Run(t, NewTwoFactorSuite())
|
||||
suite.Run(t, NewTwoFactorScenario())
|
||||
}
|
||||
|
|
99
internal/suites/scenario_user_preferences_test.go
Normal file
99
internal/suites/scenario_user_preferences_test.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type UserPreferencesScenario struct {
|
||||
*SeleniumSuite
|
||||
}
|
||||
|
||||
func NewUserPreferencesScenario() *UserPreferencesScenario {
|
||||
return &UserPreferencesScenario{
|
||||
SeleniumSuite: new(SeleniumSuite),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UserPreferencesScenario) SetupSuite() {
|
||||
wds, err := StartWebDriver()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.WebDriverSession = wds
|
||||
}
|
||||
|
||||
func (s *UserPreferencesScenario) TearDownSuite() {
|
||||
err := s.WebDriverSession.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UserPreferencesScenario) SetupTest() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doLogout(ctx, s.T())
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *UserPreferencesScenario) TestShouldRememberLastUsed2FAMethod() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Authenticate
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
|
||||
// And select OTP method
|
||||
s.doChangeMethod(ctx, s.T(), "one-time-password")
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "one-time-password-method")
|
||||
|
||||
// Then switch to push notification method
|
||||
s.doChangeMethod(ctx, s.T(), "push-notification")
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "push-notification-method")
|
||||
|
||||
// Switch context to clean up state in portal.
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
|
||||
// Then go back to portal.
|
||||
s.doVisit(s.T(), LoginBaseURL)
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
|
||||
// And check the latest method is still used.
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "push-notification-method")
|
||||
// Meaning the authentication is successful
|
||||
s.WaitElementLocatedByClassName(ctx, s.T(), "success-icon")
|
||||
|
||||
// Logout the user and see what user 'harry' sees.
|
||||
s.doLogout(ctx, s.T())
|
||||
s.doLoginOneFactor(ctx, s.T(), "harry", "password", false, "")
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "one-time-password-method")
|
||||
s.doLogout(ctx, s.T())
|
||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||
|
||||
// Then log back as previous user and verify the push notification is still the default method
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "push-notification-method")
|
||||
s.WaitElementLocatedByClassName(ctx, s.T(), "success-icon")
|
||||
|
||||
// Eventually restore the default method
|
||||
s.doChangeMethod(ctx, s.T(), "one-time-password")
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "one-time-password-method")
|
||||
}
|
||||
|
||||
func TestUserPreferencesScenario(t *testing.T) {
|
||||
suite.Run(t, NewUserPreferencesScenario())
|
||||
}
|
|
@ -9,6 +9,7 @@ var bypassAllSuiteName = "BypassAll"
|
|||
func init() {
|
||||
dockerEnvironment := NewDockerEnvironment([]string{
|
||||
"docker-compose.yml",
|
||||
"internal/suites/BypassAll/docker-compose.yml",
|
||||
"example/compose/authelia/docker-compose.backend.yml",
|
||||
"example/compose/authelia/docker-compose.frontend.yml",
|
||||
"example/compose/nginx/backend/docker-compose.yml",
|
||||
|
@ -19,7 +20,7 @@ func init() {
|
|||
})
|
||||
|
||||
setup := func(suitePath string) error {
|
||||
if err := dockerEnvironment.Up(suitePath); err != nil {
|
||||
if err := dockerEnvironment.Up(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -27,7 +28,7 @@ func init() {
|
|||
}
|
||||
|
||||
teardown := func(suitePath string) error {
|
||||
return dockerEnvironment.Down(suitePath)
|
||||
return dockerEnvironment.Down()
|
||||
}
|
||||
|
||||
GlobalRegistry.Register(bypassAllSuiteName, Suite{
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type BypassAllSuite struct {
|
||||
|
@ -12,6 +18,36 @@ func NewBypassAllSuite() *BypassAllSuite {
|
|||
return &BypassAllSuite{SeleniumSuite: new(SeleniumSuite)}
|
||||
}
|
||||
|
||||
func TestBypassAllSuite(t *testing.T) {
|
||||
RunTypescriptSuite(t, bypassAllSuiteName)
|
||||
func (s *BypassAllSuite) SetupSuite() {
|
||||
wds, err := StartWebDriver()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.WebDriverSession = wds
|
||||
}
|
||||
|
||||
func (s *BypassAllSuite) TearDownSuite() {
|
||||
err := s.WebDriverSession.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BypassAllSuite) TestShouldAccessPublicResource() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doVisit(s.T(), fmt.Sprintf("%s/secret.html", AdminBaseURL))
|
||||
s.verifySecretAuthorized(ctx, s.T())
|
||||
|
||||
s.doVisit(s.T(), fmt.Sprintf("%s/secret.html", PublicBaseURL))
|
||||
s.verifySecretAuthorized(ctx, s.T())
|
||||
}
|
||||
|
||||
func TestBypassAllSuite(t *testing.T) {
|
||||
suite.Run(t, NewBypassAllSuite())
|
||||
suite.Run(t, NewCustomHeadersScenario())
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ var duoPushSuiteName = "DuoPush"
|
|||
func init() {
|
||||
dockerEnvironment := NewDockerEnvironment([]string{
|
||||
"docker-compose.yml",
|
||||
"internal/suites/DuoPush/docker-compose.yml",
|
||||
"example/compose/authelia/docker-compose.backend.yml",
|
||||
"example/compose/authelia/docker-compose.frontend.yml",
|
||||
"example/compose/nginx/backend/docker-compose.yml",
|
||||
|
@ -17,7 +18,7 @@ func init() {
|
|||
})
|
||||
|
||||
setup := func(suitePath string) error {
|
||||
if err := dockerEnvironment.Up(suitePath); err != nil {
|
||||
if err := dockerEnvironment.Up(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -25,7 +26,7 @@ func init() {
|
|||
}
|
||||
|
||||
teardown := func(suitePath string) error {
|
||||
return dockerEnvironment.Down(suitePath)
|
||||
return dockerEnvironment.Down()
|
||||
}
|
||||
|
||||
GlobalRegistry.Register(duoPushSuiteName, Suite{
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type DuoPushSuite struct {
|
||||
|
@ -12,6 +17,58 @@ func NewDuoPushSuite() *DuoPushSuite {
|
|||
return &DuoPushSuite{SeleniumSuite: new(SeleniumSuite)}
|
||||
}
|
||||
|
||||
func TestDuoPushSuite(t *testing.T) {
|
||||
RunTypescriptSuite(t, duoPushSuiteName)
|
||||
func (s *DuoPushSuite) SetupSuite() {
|
||||
wds, err := StartWebDriver()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.WebDriverSession = wds
|
||||
}
|
||||
|
||||
func (s *DuoPushSuite) TearDownSuite() {
|
||||
err := s.WebDriverSession.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DuoPushSuite) TearDownTest() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doChangeMethod(ctx, s.T(), "one-time-password")
|
||||
}
|
||||
|
||||
func (s *DuoPushSuite) TestShouldSucceedAuthentication() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ConfigureDuo(s.T(), Allow)
|
||||
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
|
||||
s.doChangeMethod(ctx, s.T(), "push-notification")
|
||||
s.WaitElementLocatedByClassName(ctx, s.T(), "success-icon")
|
||||
}
|
||||
|
||||
func (s *DuoPushSuite) TestShouldFailAuthentication() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ConfigureDuo(s.T(), Deny)
|
||||
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
|
||||
s.doChangeMethod(ctx, s.T(), "push-notification")
|
||||
s.WaitElementLocatedByClassName(ctx, s.T(), "failure-icon")
|
||||
}
|
||||
|
||||
func TestDuoPushSuite(t *testing.T) {
|
||||
suite.Run(t, NewDuoPushSuite())
|
||||
suite.Run(t, NewAvailableMethodsScenario([]string{
|
||||
"ONE-TIME PASSWORD",
|
||||
"PUSH NOTIFICATION",
|
||||
}))
|
||||
suite.Run(t, NewUserPreferencesScenario())
|
||||
}
|
||||
|
|
|
@ -6,31 +6,32 @@ import (
|
|||
|
||||
var highAvailabilitySuiteName = "HighAvailability"
|
||||
|
||||
func init() {
|
||||
dockerEnvironment := NewDockerEnvironment([]string{
|
||||
"docker-compose.yml",
|
||||
"example/compose/authelia/docker-compose.backend.yml",
|
||||
"example/compose/authelia/docker-compose.frontend.yml",
|
||||
"example/compose/mariadb/docker-compose.yml",
|
||||
"example/compose/redis/docker-compose.yml",
|
||||
"example/compose/nginx/backend/docker-compose.yml",
|
||||
"example/compose/nginx/portal/docker-compose.yml",
|
||||
"example/compose/smtp/docker-compose.yml",
|
||||
"example/compose/httpbin/docker-compose.yml",
|
||||
"example/compose/ldap/docker-compose.admin.yml", // This is just used for administration, not for testing.
|
||||
"example/compose/ldap/docker-compose.yml",
|
||||
})
|
||||
var haDockerEnvironment = NewDockerEnvironment([]string{
|
||||
"docker-compose.yml",
|
||||
"internal/suites/HighAvailability/docker-compose.yml",
|
||||
"example/compose/authelia/docker-compose.backend.yml",
|
||||
"example/compose/authelia/docker-compose.frontend.yml",
|
||||
"example/compose/mariadb/docker-compose.yml",
|
||||
"example/compose/redis/docker-compose.yml",
|
||||
"example/compose/nginx/backend/docker-compose.yml",
|
||||
"example/compose/nginx/portal/docker-compose.yml",
|
||||
"example/compose/smtp/docker-compose.yml",
|
||||
"example/compose/httpbin/docker-compose.yml",
|
||||
"example/compose/ldap/docker-compose.admin.yml", // This is just used for administration, not for testing.
|
||||
"example/compose/ldap/docker-compose.yml",
|
||||
})
|
||||
|
||||
func init() {
|
||||
setup := func(suitePath string) error {
|
||||
if err := dockerEnvironment.Up(suitePath); err != nil {
|
||||
if err := haDockerEnvironment.Up(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||
return waitUntilAutheliaIsReady(haDockerEnvironment)
|
||||
}
|
||||
|
||||
teardown := func(suitePath string) error {
|
||||
return dockerEnvironment.Down(suitePath)
|
||||
return haDockerEnvironment.Down()
|
||||
}
|
||||
|
||||
GlobalRegistry.Register(highAvailabilitySuiteName, Suite{
|
||||
|
|
|
@ -1,18 +1,206 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type HighAvailabilitySuite struct {
|
||||
type HighAvailabilityWebDriverSuite struct {
|
||||
*SeleniumSuite
|
||||
}
|
||||
|
||||
func NewHighAvailabilityWebDriverSuite() *HighAvailabilityWebDriverSuite {
|
||||
return &HighAvailabilityWebDriverSuite{SeleniumSuite: new(SeleniumSuite)}
|
||||
}
|
||||
|
||||
func (s *HighAvailabilityWebDriverSuite) SetupSuite() {
|
||||
wds, err := StartWebDriver()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.WebDriverSession = wds
|
||||
}
|
||||
|
||||
func (s *HighAvailabilityWebDriverSuite) TearDownSuite() {
|
||||
err := s.WebDriverSession.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserDataInDB() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password")
|
||||
|
||||
err := haDockerEnvironment.Restart("mariadb")
|
||||
s.Assert().NoError(err)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "")
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *HighAvailabilityWebDriverSuite) TestShouldKeepSessionAfterAutheliaRestart() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
secret := s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, "")
|
||||
|
||||
err := haDockerEnvironment.Restart("authelia-backend")
|
||||
s.Assert().NoError(err)
|
||||
|
||||
loop := true
|
||||
for loop {
|
||||
logs, err := haDockerEnvironment.Logs("authelia-backend", []string{"--tail", "10"})
|
||||
s.Assert().NoError(err)
|
||||
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
if strings.Contains(logs, "Authelia is listening on :9091") {
|
||||
loop = false
|
||||
}
|
||||
break
|
||||
case <-ctx.Done():
|
||||
loop = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
|
||||
// Verify the user is still authenticated
|
||||
s.doVisit(s.T(), LoginBaseURL)
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
|
||||
// Then logout and login again to check the secret is still there
|
||||
s.doLogout(ctx, s.T())
|
||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||
|
||||
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, fmt.Sprintf("%s/secret.html", SecureBaseURL))
|
||||
s.verifySecretAuthorized(ctx, s.T())
|
||||
}
|
||||
|
||||
var UserJohn = "john"
|
||||
var UserBob = "bob"
|
||||
var UserHarry = "harry"
|
||||
|
||||
var Users = []string{UserJohn, UserBob, UserHarry}
|
||||
|
||||
var expectedAuthorizations = map[string](map[string]bool){
|
||||
fmt.Sprintf("%s/secret.html", PublicBaseURL): map[string]bool{
|
||||
UserJohn: true, UserBob: true, UserHarry: true,
|
||||
},
|
||||
fmt.Sprintf("%s/secret.html", SecureBaseURL): map[string]bool{
|
||||
UserJohn: true, UserBob: true, UserHarry: true,
|
||||
},
|
||||
fmt.Sprintf("%s/secret.html", AdminBaseURL): map[string]bool{
|
||||
UserJohn: true, UserBob: false, UserHarry: false,
|
||||
},
|
||||
fmt.Sprintf("%s/secret.html", SingleFactorBaseURL): map[string]bool{
|
||||
UserJohn: true, UserBob: true, UserHarry: true,
|
||||
},
|
||||
fmt.Sprintf("%s/secret.html", MX1MailBaseURL): map[string]bool{
|
||||
UserJohn: true, UserBob: true, UserHarry: false,
|
||||
},
|
||||
fmt.Sprintf("%s/secret.html", MX2MailBaseURL): map[string]bool{
|
||||
UserJohn: false, UserBob: true, UserHarry: false,
|
||||
},
|
||||
|
||||
fmt.Sprintf("%s/groups/admin/secret.html", DevBaseURL): map[string]bool{
|
||||
UserJohn: true, UserBob: false, UserHarry: false,
|
||||
},
|
||||
fmt.Sprintf("%s/groups/dev/secret.html", DevBaseURL): map[string]bool{
|
||||
UserJohn: true, UserBob: true, UserHarry: false,
|
||||
},
|
||||
fmt.Sprintf("%s/users/john/secret.html", DevBaseURL): map[string]bool{
|
||||
UserJohn: true, UserBob: false, UserHarry: false,
|
||||
},
|
||||
fmt.Sprintf("%s/users/harry/secret.html", DevBaseURL): map[string]bool{
|
||||
UserJohn: true, UserBob: false, UserHarry: true,
|
||||
},
|
||||
fmt.Sprintf("%s/users/bob/secret.html", DevBaseURL): map[string]bool{
|
||||
UserJohn: true, UserBob: true, UserHarry: false,
|
||||
},
|
||||
}
|
||||
|
||||
func (s *HighAvailabilityWebDriverSuite) TestShouldVerifyAccessControl() {
|
||||
verifyUserIsAuthorized := func(ctx context.Context, t *testing.T, username, targetURL string, authorized bool) {
|
||||
s.doVisit(t, targetURL)
|
||||
s.verifyURLIs(ctx, t, targetURL)
|
||||
if authorized {
|
||||
s.verifySecretAuthorized(ctx, t)
|
||||
} else {
|
||||
s.verifyBodyContains(ctx, t, "403 Forbidden")
|
||||
}
|
||||
}
|
||||
|
||||
verifyAuthorization := func(username string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doRegisterAndLogin2FA(ctx, t, username, "password", false, "")
|
||||
|
||||
for url, authorizations := range expectedAuthorizations {
|
||||
verifyUserIsAuthorized(ctx, t, username, url, authorizations[username])
|
||||
}
|
||||
|
||||
s.doLogout(ctx, t)
|
||||
}
|
||||
}
|
||||
|
||||
for _, user := range []string{UserJohn, UserBob, UserHarry} {
|
||||
s.T().Run(fmt.Sprintf("user %s", user), verifyAuthorization(user))
|
||||
}
|
||||
}
|
||||
|
||||
type HighAvailabilitySuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func NewHighAvailabilitySuite() *HighAvailabilitySuite {
|
||||
return &HighAvailabilitySuite{SeleniumSuite: new(SeleniumSuite)}
|
||||
return &HighAvailabilitySuite{}
|
||||
}
|
||||
|
||||
func DoGetWithAuth(t *testing.T, username, password string) int {
|
||||
client := NewHTTPClient()
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/secret.html", SingleFactorBaseURL), nil)
|
||||
req.SetBasicAuth(username, password)
|
||||
assert.NoError(t, err)
|
||||
|
||||
res, err := client.Do(req)
|
||||
assert.NoError(t, err)
|
||||
return res.StatusCode
|
||||
}
|
||||
|
||||
func (s *HighAvailabilitySuite) TestBasicAuth() {
|
||||
s.Assert().Equal(DoGetWithAuth(s.T(), "john", "password"), 200)
|
||||
s.Assert().Equal(DoGetWithAuth(s.T(), "john", "bad-password"), 302)
|
||||
s.Assert().Equal(DoGetWithAuth(s.T(), "dontexist", "password"), 302)
|
||||
|
||||
}
|
||||
|
||||
func TestHighAvailabilitySuite(t *testing.T) {
|
||||
RunTypescriptSuite(t, highAvailabilitySuiteName)
|
||||
TestRunOneFactor(t)
|
||||
suite.Run(t, NewOneFactorScenario())
|
||||
suite.Run(t, NewTwoFactorScenario())
|
||||
suite.Run(t, NewRegulationScenario())
|
||||
suite.Run(t, NewCustomHeadersScenario())
|
||||
suite.Run(t, NewRedirectionCheckScenario())
|
||||
suite.Run(t, NewHighAvailabilityWebDriverSuite())
|
||||
suite.Run(t, NewHighAvailabilitySuite())
|
||||
}
|
||||
|
|
|
@ -15,6 +15,6 @@ func NewKubernetesSuite() *KubernetesSuite {
|
|||
}
|
||||
|
||||
func TestKubernetesSuite(t *testing.T) {
|
||||
suite.Run(t, NewOneFactorSuite())
|
||||
suite.Run(t, NewTwoFactorSuite())
|
||||
suite.Run(t, NewOneFactorScenario())
|
||||
suite.Run(t, NewTwoFactorScenario())
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ var ldapSuiteName = "LDAP"
|
|||
func init() {
|
||||
dockerEnvironment := NewDockerEnvironment([]string{
|
||||
"docker-compose.yml",
|
||||
"internal/suites/LDAP/docker-compose.yml",
|
||||
"example/compose/authelia/docker-compose.backend.yml",
|
||||
"example/compose/authelia/docker-compose.frontend.yml",
|
||||
"example/compose/nginx/backend/docker-compose.yml",
|
||||
|
@ -18,7 +19,7 @@ func init() {
|
|||
})
|
||||
|
||||
setup := func(suitePath string) error {
|
||||
err := dockerEnvironment.Up(suitePath)
|
||||
err := dockerEnvironment.Up()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -28,7 +29,7 @@ func init() {
|
|||
}
|
||||
|
||||
teardown := func(suitePath string) error {
|
||||
err := dockerEnvironment.Down(suitePath)
|
||||
err := dockerEnvironment.Down()
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,6 @@ func NewLDAPSuite() *LDAPSuite {
|
|||
}
|
||||
|
||||
func TestLDAPSuite(t *testing.T) {
|
||||
suite.Run(t, NewOneFactorSuite())
|
||||
suite.Run(t, NewTwoFactorSuite())
|
||||
suite.Run(t, NewOneFactorScenario())
|
||||
suite.Run(t, NewTwoFactorScenario())
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ var mariadbSuiteName = "Mariadb"
|
|||
func init() {
|
||||
dockerEnvironment := NewDockerEnvironment([]string{
|
||||
"docker-compose.yml",
|
||||
"internal/suites/Mariadb/docker-compose.yml",
|
||||
"example/compose/authelia/docker-compose.backend.yml",
|
||||
"example/compose/authelia/docker-compose.frontend.yml",
|
||||
"example/compose/nginx/backend/docker-compose.yml",
|
||||
|
@ -19,7 +20,7 @@ func init() {
|
|||
})
|
||||
|
||||
setup := func(suitePath string) error {
|
||||
if err := dockerEnvironment.Up(suitePath); err != nil {
|
||||
if err := dockerEnvironment.Up(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -27,7 +28,7 @@ func init() {
|
|||
}
|
||||
|
||||
teardown := func(suitePath string) error {
|
||||
err := dockerEnvironment.Down(suitePath)
|
||||
err := dockerEnvironment.Down()
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,6 @@ func NewMariadbSuite() *MariadbSuite {
|
|||
}
|
||||
|
||||
func TestMariadbSuite(t *testing.T) {
|
||||
suite.Run(t, NewOneFactorSuite())
|
||||
suite.Run(t, NewTwoFactorSuite())
|
||||
suite.Run(t, NewOneFactorScenario())
|
||||
suite.Run(t, NewTwoFactorScenario())
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ var networkACLSuiteName = "NetworkACL"
|
|||
func init() {
|
||||
dockerEnvironment := NewDockerEnvironment([]string{
|
||||
"docker-compose.yml",
|
||||
"internal/suites/NetworkACL/docker-compose.yml",
|
||||
"example/compose/authelia/docker-compose.backend.yml",
|
||||
"example/compose/authelia/docker-compose.frontend.yml",
|
||||
"example/compose/nginx/backend/docker-compose.yml",
|
||||
|
@ -20,7 +21,7 @@ func init() {
|
|||
})
|
||||
|
||||
setup := func(suitePath string) error {
|
||||
if err := dockerEnvironment.Up(suitePath); err != nil {
|
||||
if err := dockerEnvironment.Up(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -28,7 +29,7 @@ func init() {
|
|||
}
|
||||
|
||||
teardown := func(suitePath string) error {
|
||||
return dockerEnvironment.Down(suitePath)
|
||||
return dockerEnvironment.Down()
|
||||
}
|
||||
|
||||
GlobalRegistry.Register(networkACLSuiteName, Suite{
|
||||
|
|
|
@ -1,17 +1,102 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type NetworkACLSuite struct {
|
||||
*SeleniumSuite
|
||||
suite.Suite
|
||||
|
||||
clients []*WebDriverSession
|
||||
}
|
||||
|
||||
func NewNetworkACLSuite() *NetworkACLSuite {
|
||||
return &NetworkACLSuite{SeleniumSuite: new(SeleniumSuite)}
|
||||
return &NetworkACLSuite{clients: make([]*WebDriverSession, 3)}
|
||||
}
|
||||
|
||||
func (s *NetworkACLSuite) createClient(idx int) {
|
||||
wds, err := StartWebDriverWithProxy(fmt.Sprintf("http://proxy-client%d.example.com:3128", idx), 4444+idx)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.clients[idx] = wds
|
||||
}
|
||||
|
||||
func (s *NetworkACLSuite) teardownClient(idx int) {
|
||||
if err := s.clients[idx].Stop(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *NetworkACLSuite) SetupSuite() {
|
||||
wds, err := StartWebDriver()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
s.clients[0] = wds
|
||||
|
||||
for i := 1; i <= 2; i++ {
|
||||
s.createClient(i)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *NetworkACLSuite) TearDownSuite() {
|
||||
if err := s.clients[0].Stop(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for i := 1; i <= 2; i++ {
|
||||
s.teardownClient(i)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *NetworkACLSuite) TestShouldAccessSecretUpon2FA() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL)
|
||||
secret := s.clients[0].doRegisterThenLogout(ctx, s.T(), "john", "password")
|
||||
|
||||
s.clients[0].doVisit(s.T(), targetURL)
|
||||
s.clients[0].verifyIsFirstFactorPage(ctx, s.T())
|
||||
|
||||
s.clients[0].doLoginOneFactor(ctx, s.T(), "john", "password", false, targetURL)
|
||||
s.clients[0].verifyIsSecondFactorPage(ctx, s.T())
|
||||
s.clients[0].doValidateTOTP(ctx, s.T(), secret)
|
||||
|
||||
s.clients[0].verifySecretAuthorized(ctx, s.T())
|
||||
}
|
||||
|
||||
// from network 192.168.240.201/32
|
||||
func (s *NetworkACLSuite) TestShouldAccessSecretUpon1FA() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL)
|
||||
s.clients[1].doVisit(s.T(), targetURL)
|
||||
s.clients[1].verifyIsFirstFactorPage(ctx, s.T())
|
||||
|
||||
s.clients[1].doLoginOneFactor(ctx, s.T(), "john", "password",
|
||||
false, fmt.Sprintf("%s/secret.html", SecureBaseURL))
|
||||
s.clients[1].verifySecretAuthorized(ctx, s.T())
|
||||
}
|
||||
|
||||
// from network 192.168.240.202/32
|
||||
func (s *NetworkACLSuite) TestShouldAccessSecretUpon0FA() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.clients[2].doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL))
|
||||
s.clients[2].verifySecretAuthorized(ctx, s.T())
|
||||
}
|
||||
|
||||
func TestNetworkACLSuite(t *testing.T) {
|
||||
RunTypescriptSuite(t, networkACLSuiteName)
|
||||
suite.Run(t, NewNetworkACLSuite())
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ var postgresSuiteName = "Postgres"
|
|||
func init() {
|
||||
dockerEnvironment := NewDockerEnvironment([]string{
|
||||
"docker-compose.yml",
|
||||
"internal/suites/Postgres/docker-compose.yml",
|
||||
"example/compose/authelia/docker-compose.backend.yml",
|
||||
"example/compose/authelia/docker-compose.frontend.yml",
|
||||
"example/compose/nginx/backend/docker-compose.yml",
|
||||
|
@ -19,7 +20,7 @@ func init() {
|
|||
})
|
||||
|
||||
setup := func(suitePath string) error {
|
||||
if err := dockerEnvironment.Up(suitePath); err != nil {
|
||||
if err := dockerEnvironment.Up(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -27,7 +28,7 @@ func init() {
|
|||
}
|
||||
|
||||
teardown := func(suitePath string) error {
|
||||
err := dockerEnvironment.Down(suitePath)
|
||||
err := dockerEnvironment.Down()
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,6 @@ func NewPostgresSuite() *PostgresSuite {
|
|||
}
|
||||
|
||||
func TestPostgresSuite(t *testing.T) {
|
||||
suite.Run(t, NewOneFactorSuite())
|
||||
suite.Run(t, NewTwoFactorSuite())
|
||||
suite.Run(t, NewOneFactorScenario())
|
||||
suite.Run(t, NewTwoFactorScenario())
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ var shortTimeoutsSuiteName = "ShortTimeouts"
|
|||
func init() {
|
||||
dockerEnvironment := NewDockerEnvironment([]string{
|
||||
"docker-compose.yml",
|
||||
"internal/suites/ShortTimeouts/docker-compose.yml",
|
||||
"example/compose/authelia/docker-compose.backend.yml",
|
||||
"example/compose/authelia/docker-compose.frontend.yml",
|
||||
"example/compose/nginx/backend/docker-compose.yml",
|
||||
|
@ -17,7 +18,7 @@ func init() {
|
|||
})
|
||||
|
||||
setup := func(suitePath string) error {
|
||||
if err := dockerEnvironment.Up(suitePath); err != nil {
|
||||
if err := dockerEnvironment.Up(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -25,7 +26,7 @@ func init() {
|
|||
}
|
||||
|
||||
teardown := func(suitePath string) error {
|
||||
return dockerEnvironment.Down(suitePath)
|
||||
return dockerEnvironment.Down()
|
||||
}
|
||||
|
||||
GlobalRegistry.Register(shortTimeoutsSuiteName, Suite{
|
||||
|
|
|
@ -2,6 +2,8 @@ package suites
|
|||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type ShortTimeoutsSuite struct {
|
||||
|
@ -13,5 +15,6 @@ func NewShortTimeoutsSuite() *ShortTimeoutsSuite {
|
|||
}
|
||||
|
||||
func TestShortTimeoutsSuite(t *testing.T) {
|
||||
RunTypescriptSuite(t, shortTimeoutsSuiteName)
|
||||
suite.Run(t, NewInactivityScenario())
|
||||
suite.Run(t, NewRegulationScenario())
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ var standaloneSuiteName = "Standalone"
|
|||
func init() {
|
||||
dockerEnvironment := NewDockerEnvironment([]string{
|
||||
"docker-compose.yml",
|
||||
"internal/suites/Standalone/docker-compose.yml",
|
||||
"example/compose/authelia/docker-compose.backend.yml",
|
||||
"example/compose/authelia/docker-compose.frontend.yml",
|
||||
"example/compose/nginx/backend/docker-compose.yml",
|
||||
|
@ -17,7 +18,7 @@ func init() {
|
|||
})
|
||||
|
||||
setup := func(suitePath string) error {
|
||||
err := dockerEnvironment.Up(suitePath)
|
||||
err := dockerEnvironment.Up()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -27,7 +28,7 @@ func init() {
|
|||
}
|
||||
|
||||
teardown := func(suitePath string) error {
|
||||
err := dockerEnvironment.Down(suitePath)
|
||||
err := dockerEnvironment.Down()
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +1,138 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type StandaloneSuite struct {
|
||||
type StandaloneWebDriverSuite struct {
|
||||
*SeleniumSuite
|
||||
}
|
||||
|
||||
func NewStandaloneWebDriverSuite() *StandaloneWebDriverSuite {
|
||||
return &StandaloneWebDriverSuite{SeleniumSuite: new(SeleniumSuite)}
|
||||
}
|
||||
|
||||
func (s *StandaloneWebDriverSuite) SetupSuite() {
|
||||
wds, err := StartWebDriver()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s.WebDriverSession = wds
|
||||
}
|
||||
|
||||
func (s *StandaloneWebDriverSuite) TearDownSuite() {
|
||||
err := s.WebDriverSession.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StandaloneWebDriverSuite) SetupTest() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.doLogout(ctx, s.T())
|
||||
s.WebDriverSession.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *StandaloneWebDriverSuite) TestShouldLetUserKnowHeIsAlreadyAuthenticated() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_ = s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, "")
|
||||
|
||||
// Visit home page to change context
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
|
||||
// Visit the login page and wait for redirection to 2FA page with success icon displayed
|
||||
s.doVisit(s.T(), LoginBaseURL)
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
|
||||
// Check whether the success icon is displayed
|
||||
s.WaitElementLocatedByClassName(ctx, s.T(), "success-icon")
|
||||
}
|
||||
|
||||
type StandaloneSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func NewStandaloneSuite() *StandaloneSuite {
|
||||
return &StandaloneSuite{SeleniumSuite: new(SeleniumSuite)}
|
||||
return &StandaloneSuite{}
|
||||
}
|
||||
|
||||
// Standard case using nginx
|
||||
func (s *StandaloneSuite) TestShouldVerifyAPIVerifyUnauthorize() {
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/verify", AutheliaBaseURL), nil)
|
||||
s.Assert().NoError(err)
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
req.Header.Set("X-Original-URL", AdminBaseURL)
|
||||
|
||||
client := NewHTTPClient()
|
||||
res, err := client.Do(req)
|
||||
s.Assert().NoError(err)
|
||||
s.Assert().Equal(res.StatusCode, 401)
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
s.Assert().NoError(err)
|
||||
s.Assert().Equal(string(body), "Unauthorized")
|
||||
}
|
||||
|
||||
// Standard case using Kubernetes
|
||||
func (s *StandaloneSuite) TestShouldVerifyAPIVerifyRedirectFromXOriginalURL() {
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/verify?rd=%s", AutheliaBaseURL, LoginBaseURL), nil)
|
||||
s.Assert().NoError(err)
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
req.Header.Set("X-Original-URL", AdminBaseURL)
|
||||
|
||||
client := NewHTTPClient()
|
||||
res, err := client.Do(req)
|
||||
s.Assert().NoError(err)
|
||||
s.Assert().Equal(res.StatusCode, 302)
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
s.Assert().NoError(err)
|
||||
s.Assert().Equal(string(body), fmt.Sprintf("Found. Redirecting to %s?rd=%s", LoginBaseURL, AdminBaseURL))
|
||||
}
|
||||
|
||||
func (s *StandaloneSuite) TestShouldVerifyAPIVerifyRedirectFromXOriginalHostURI() {
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/verify?rd=%s", AutheliaBaseURL, LoginBaseURL), nil)
|
||||
s.Assert().NoError(err)
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
req.Header.Set("X-Forwarded-Host", "secure.example.com:8080")
|
||||
req.Header.Set("X-Forwarded-URI", "/")
|
||||
|
||||
client := NewHTTPClient()
|
||||
res, err := client.Do(req)
|
||||
s.Assert().NoError(err)
|
||||
s.Assert().Equal(res.StatusCode, 302)
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
s.Assert().NoError(err)
|
||||
s.Assert().Equal(string(body), fmt.Sprintf("Found. Redirecting to %s?rd=https://secure.example.com:8080/", LoginBaseURL))
|
||||
}
|
||||
|
||||
func TestStandaloneWebDriverScenario(t *testing.T) {
|
||||
suite.Run(t, NewStandaloneWebDriverSuite())
|
||||
}
|
||||
|
||||
func TestStandaloneSuite(t *testing.T) {
|
||||
suite.Run(t, NewOneFactorSuite())
|
||||
suite.Run(t, NewTwoFactorSuite())
|
||||
suite.Run(t, NewOneFactorScenario())
|
||||
suite.Run(t, NewTwoFactorScenario())
|
||||
suite.Run(t, NewBypassPolicyScenario())
|
||||
suite.Run(t, NewBackendProtectionScenario())
|
||||
suite.Run(t, NewResetPasswordScenario())
|
||||
suite.Run(t, NewAvailableMethodsScenario([]string{"ONE-TIME PASSWORD"}))
|
||||
|
||||
RunTypescriptSuite(t, standaloneSuiteName)
|
||||
suite.Run(t, NewStandaloneWebDriverSuite())
|
||||
suite.Run(t, NewStandaloneSuite())
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ var traefikSuiteName = "Traefik"
|
|||
func init() {
|
||||
dockerEnvironment := NewDockerEnvironment([]string{
|
||||
"docker-compose.yml",
|
||||
"internal/suites/Traefik/docker-compose.yml",
|
||||
"example/compose/authelia/docker-compose.backend.yml",
|
||||
"example/compose/authelia/docker-compose.frontend.yml",
|
||||
"example/compose/nginx/backend/docker-compose.yml",
|
||||
|
@ -17,7 +18,7 @@ func init() {
|
|||
})
|
||||
|
||||
setup := func(suitePath string) error {
|
||||
err := dockerEnvironment.Up(suitePath)
|
||||
err := dockerEnvironment.Up()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -27,7 +28,7 @@ func init() {
|
|||
}
|
||||
|
||||
teardown := func(suitePath string) error {
|
||||
err := dockerEnvironment.Down(suitePath)
|
||||
err := dockerEnvironment.Down()
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,6 @@ func NewTraefikSuite() *TraefikSuite {
|
|||
}
|
||||
|
||||
func TestTraefikSuite(t *testing.T) {
|
||||
suite.Run(t, NewOneFactorSuite())
|
||||
suite.Run(t, NewTwoFactorSuite())
|
||||
suite.Run(t, NewOneFactorScenario())
|
||||
suite.Run(t, NewTwoFactorScenario())
|
||||
}
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/clems4ever/authelia/internal/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/tebeka/selenium"
|
||||
)
|
||||
|
@ -24,55 +16,3 @@ type SeleniumSuite struct {
|
|||
func (s *SeleniumSuite) WebDriver() selenium.WebDriver {
|
||||
return s.WebDriverSession.WebDriver
|
||||
}
|
||||
|
||||
// Wait wait until condition holds true
|
||||
func (s *SeleniumSuite) Wait(ctx context.Context, condition selenium.Condition) error {
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- s.WebDriverSession.WebDriver.Wait(condition)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.New("waiting timeout reached")
|
||||
case err := <-done:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func rootPath() string {
|
||||
rootPath := os.Getenv("ROOT_PATH")
|
||||
|
||||
// If env variable is not provided, use relative path.
|
||||
if rootPath == "" {
|
||||
rootPath = "../.."
|
||||
}
|
||||
return rootPath
|
||||
}
|
||||
|
||||
func relativePath(path string) string {
|
||||
return fmt.Sprintf("%s/%s", rootPath(), path)
|
||||
}
|
||||
|
||||
// RunTypescriptSuite run the tests of the typescript suite
|
||||
func RunTypescriptSuite(t *testing.T, suite string) {
|
||||
forbidFlags := ""
|
||||
if os.Getenv("ONLY_FORBIDDEN") == "true" {
|
||||
forbidFlags = "--forbid-only --forbid-pending"
|
||||
}
|
||||
|
||||
cmdline := "./node_modules/.bin/mocha" +
|
||||
" --exit --require ts-node/register " + forbidFlags + " " +
|
||||
fmt.Sprintf("test/suites/%s/test.ts", suite)
|
||||
|
||||
command := utils.CommandWithStdout("bash", "-c", cmdline)
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
command.Dir = rootPath()
|
||||
command.Env = append(
|
||||
os.Environ(),
|
||||
"ENVIRONMENT=dev",
|
||||
fmt.Sprintf("TS_NODE_PROJECT=%s", "test/tsconfig.json"))
|
||||
|
||||
assert.NoError(t, command.Run())
|
||||
}
|
||||
|
|
|
@ -1,8 +1,26 @@
|
|||
package suites
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
func verifyBodyContains(ctx context.Context, s *SeleniumSuite, pattern string) {
|
||||
bodyElement := WaitElementLocatedByTagName(ctx, s, "body")
|
||||
WaitElementTextContains(ctx, s, bodyElement, pattern)
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tebeka/selenium"
|
||||
)
|
||||
|
||||
func (wds *WebDriverSession) verifyBodyContains(ctx context.Context, t *testing.T, pattern string) {
|
||||
err := wds.Wait(ctx, func(wd selenium.WebDriver) (bool, error) {
|
||||
bodyElement := wds.WaitElementLocatedByTagName(ctx, t, "body")
|
||||
require.NotNil(t, bodyElement)
|
||||
|
||||
content, err := bodyElement.Text()
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return strings.Contains(content, pattern), nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package suites
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func verifyIsFirstFactorPage(ctx context.Context, s *SeleniumSuite) {
|
||||
WaitElementLocatedByClassName(ctx, s, "first-factor-step")
|
||||
func (wds *WebDriverSession) verifyIsFirstFactorPage(ctx context.Context, t *testing.T) {
|
||||
wds.WaitElementLocatedByID(ctx, t, "first-factor-stage")
|
||||
}
|
||||
|
|
11
internal/suites/verify_is_home.go
Normal file
11
internal/suites/verify_is_home.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func (wds *WebDriverSession) verifyIsHome(ctx context.Context, t *testing.T) {
|
||||
wds.verifyURLIs(ctx, t, fmt.Sprintf("%s/", HomeBaseURL))
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
package suites
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func verifyIsSecondFactorPage(ctx context.Context, s *SeleniumSuite) {
|
||||
WaitElementLocatedByClassName(ctx, s, "second-factor-step")
|
||||
func (wds *WebDriverSession) verifyIsSecondFactorPage(ctx context.Context, t *testing.T) {
|
||||
wds.WaitElementLocatedByID(ctx, t, "second-factor-stage")
|
||||
}
|
||||
|
|
10
internal/suites/verify_mail.go
Normal file
10
internal/suites/verify_mail.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func (wds *WebDriverSession) verifyMailNotificationDisplayed(ctx context.Context, t *testing.T) {
|
||||
wds.verifyNotificationDisplayed(ctx, t, "An email has been sent to your address to complete the process.")
|
||||
}
|
|
@ -1,9 +1,14 @@
|
|||
package suites
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
func verifyNotificationDisplayed(ctx context.Context, s *SeleniumSuite, message string) {
|
||||
txt, err := WaitElementLocatedByClassName(ctx, s, "notification").Text()
|
||||
s.Assert().NoError(err)
|
||||
s.Assert().Equal(message, txt)
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func (wds *WebDriverSession) verifyNotificationDisplayed(ctx context.Context, t *testing.T, message string) {
|
||||
el := wds.WaitElementLocatedByClassName(ctx, t, "notification")
|
||||
assert.NotNil(t, el)
|
||||
wds.WaitElementTextContains(ctx, t, el, message)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package suites
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func verifySecretAuthorized(ctx context.Context, s *SeleniumSuite) {
|
||||
verifyBodyContains(ctx, s, "This is a very important secret!")
|
||||
func (wds *WebDriverSession) verifySecretAuthorized(ctx context.Context, t *testing.T) {
|
||||
wds.verifyBodyContains(ctx, t, "This is a very important secret!")
|
||||
}
|
||||
|
|
|
@ -2,21 +2,21 @@ package suites
|
|||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tebeka/selenium"
|
||||
)
|
||||
|
||||
func verifyURLIs(ctx context.Context, s *SeleniumSuite, url string) {
|
||||
err := s.Wait(ctx, func(driver selenium.WebDriver) (bool, error) {
|
||||
func (wds *WebDriverSession) verifyURLIs(ctx context.Context, t *testing.T, url string) {
|
||||
err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) {
|
||||
currentURL, err := driver.CurrentURL()
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return currentURL == url, nil
|
||||
})
|
||||
|
||||
assert.NoError(s.T(), err)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
|
@ -2,11 +2,13 @@ package suites
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tebeka/selenium"
|
||||
"github.com/tebeka/selenium/chrome"
|
||||
)
|
||||
|
@ -17,9 +19,8 @@ type WebDriverSession struct {
|
|||
WebDriver selenium.WebDriver
|
||||
}
|
||||
|
||||
// StartWebDriver create a selenium session
|
||||
func StartWebDriver() (*WebDriverSession, error) {
|
||||
port := 4444
|
||||
// StartWebDriverWithProxy create a selenium session
|
||||
func StartWebDriverWithProxy(proxy string, port int) (*WebDriverSession, error) {
|
||||
service, err := selenium.NewChromeDriverService("/usr/bin/chromedriver", port)
|
||||
|
||||
if err != nil {
|
||||
|
@ -34,6 +35,10 @@ func StartWebDriver() (*WebDriverSession, error) {
|
|||
chromeCaps.Args = append(chromeCaps.Args, "--headless")
|
||||
}
|
||||
|
||||
if proxy != "" {
|
||||
chromeCaps.Args = append(chromeCaps.Args, fmt.Sprintf("--proxy-server=%s", proxy))
|
||||
}
|
||||
|
||||
caps := selenium.Capabilities{}
|
||||
caps.AddChrome(chromeCaps)
|
||||
|
||||
|
@ -49,6 +54,11 @@ func StartWebDriver() (*WebDriverSession, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
// StartWebDriver create a selenium session
|
||||
func StartWebDriver() (*WebDriverSession, error) {
|
||||
return StartWebDriverWithProxy("", 4444)
|
||||
}
|
||||
|
||||
// Stop stop the selenium session
|
||||
func (wds *WebDriverSession) Stop() error {
|
||||
err := wds.WebDriver.Quit()
|
||||
|
@ -73,9 +83,24 @@ func WithWebdriver(fn func(webdriver selenium.WebDriver) error) error {
|
|||
return fn(wds.WebDriver)
|
||||
}
|
||||
|
||||
func waitElementLocated(ctx context.Context, s *SeleniumSuite, by, value string) selenium.WebElement {
|
||||
// Wait wait until condition holds true
|
||||
func (wds *WebDriverSession) Wait(ctx context.Context, condition selenium.Condition) error {
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- wds.WebDriver.Wait(condition)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.New("waiting timeout reached")
|
||||
case err := <-done:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (wds *WebDriverSession) waitElementLocated(ctx context.Context, t *testing.T, by, value string) selenium.WebElement {
|
||||
var el selenium.WebElement
|
||||
err := s.Wait(ctx, func(driver selenium.WebDriver) (bool, error) {
|
||||
err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) {
|
||||
var err error
|
||||
el, err = driver.FindElement(by, value)
|
||||
|
||||
|
@ -89,31 +114,65 @@ func waitElementLocated(ctx context.Context, s *SeleniumSuite, by, value string)
|
|||
return el != nil, nil
|
||||
})
|
||||
|
||||
assert.NoError(s.T(), err)
|
||||
assert.NotNil(s.T(), el, "Element has not been located")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, el)
|
||||
return el
|
||||
}
|
||||
|
||||
func (wds *WebDriverSession) waitElementsLocated(ctx context.Context, t *testing.T, by, value string) []selenium.WebElement {
|
||||
var el []selenium.WebElement
|
||||
err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) {
|
||||
var err error
|
||||
el, err = driver.FindElements(by, value)
|
||||
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no such element") {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
return el != nil, nil
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, el)
|
||||
return el
|
||||
}
|
||||
|
||||
// WaitElementLocatedByID wait an element is located by id
|
||||
func WaitElementLocatedByID(ctx context.Context, s *SeleniumSuite, id string) selenium.WebElement {
|
||||
return waitElementLocated(ctx, s, selenium.ByID, id)
|
||||
func (wds *WebDriverSession) WaitElementLocatedByID(ctx context.Context, t *testing.T, id string) selenium.WebElement {
|
||||
return wds.waitElementLocated(ctx, t, selenium.ByID, id)
|
||||
}
|
||||
|
||||
// WaitElementLocatedByTagName wait an element is located by tag name
|
||||
func WaitElementLocatedByTagName(ctx context.Context, s *SeleniumSuite, tagName string) selenium.WebElement {
|
||||
return waitElementLocated(ctx, s, selenium.ByTagName, tagName)
|
||||
func (wds *WebDriverSession) WaitElementLocatedByTagName(ctx context.Context, t *testing.T, tagName string) selenium.WebElement {
|
||||
return wds.waitElementLocated(ctx, t, selenium.ByTagName, tagName)
|
||||
}
|
||||
|
||||
// WaitElementLocatedByClassName wait an element is located by class name
|
||||
func WaitElementLocatedByClassName(ctx context.Context, s *SeleniumSuite, className string) selenium.WebElement {
|
||||
return waitElementLocated(ctx, s, selenium.ByClassName, className)
|
||||
func (wds *WebDriverSession) WaitElementLocatedByClassName(ctx context.Context, t *testing.T, className string) selenium.WebElement {
|
||||
return wds.waitElementLocated(ctx, t, selenium.ByClassName, className)
|
||||
}
|
||||
|
||||
// WaitElementLocatedByLinkText wait an element is located by link text
|
||||
func (wds *WebDriverSession) WaitElementLocatedByLinkText(ctx context.Context, t *testing.T, linkText string) selenium.WebElement {
|
||||
return wds.waitElementLocated(ctx, t, selenium.ByLinkText, linkText)
|
||||
}
|
||||
|
||||
// WaitElementLocatedByCSSSelector wait an element is located by class name
|
||||
func (wds *WebDriverSession) WaitElementLocatedByCSSSelector(ctx context.Context, t *testing.T, cssSelector string) selenium.WebElement {
|
||||
return wds.waitElementLocated(ctx, t, selenium.ByCSSSelector, cssSelector)
|
||||
}
|
||||
|
||||
// WaitElementsLocatedByCSSSelector wait an element is located by CSS selector
|
||||
func (wds *WebDriverSession) WaitElementsLocatedByCSSSelector(ctx context.Context, t *testing.T, cssSelector string) []selenium.WebElement {
|
||||
return wds.waitElementsLocated(ctx, t, selenium.ByCSSSelector, cssSelector)
|
||||
}
|
||||
|
||||
// WaitElementTextContains wait the text of an element contains a pattern
|
||||
func WaitElementTextContains(ctx context.Context, s *SeleniumSuite, element selenium.WebElement, pattern string) {
|
||||
assert.NotNil(s.T(), element)
|
||||
|
||||
s.Wait(ctx, func(driver selenium.WebDriver) (bool, error) {
|
||||
func (wds *WebDriverSession) WaitElementTextContains(ctx context.Context, t *testing.T, element selenium.WebElement, pattern string) {
|
||||
err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) {
|
||||
text, err := element.Text()
|
||||
|
||||
if err != nil {
|
||||
|
@ -122,4 +181,5 @@ func WaitElementTextContains(ctx context.Context, s *SeleniumSuite, element sele
|
|||
|
||||
return strings.Contains(text, pattern), nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
|
22
internal/utils/clock.go
Normal file
22
internal/utils/clock.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package utils
|
||||
|
||||
import "time"
|
||||
|
||||
// Clock is an interface for a clock
|
||||
type Clock interface {
|
||||
Now() time.Time
|
||||
After(d time.Duration) <-chan time.Time
|
||||
}
|
||||
|
||||
// RealClock is the implementation of a clock for production code
|
||||
type RealClock struct{}
|
||||
|
||||
// Now return the current time
|
||||
func (RealClock) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// After return a channel receiving the time after the defined duration
|
||||
func (RealClock) After(d time.Duration) <-chan time.Time {
|
||||
return time.After(d)
|
||||
}
|
|
@ -6,6 +6,8 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
@ -13,9 +15,22 @@ import (
|
|||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Command create a command at the project root
|
||||
func Command(name string, args ...string) *exec.Cmd {
|
||||
cmd := exec.Command(name, args...)
|
||||
|
||||
// By default set the working directory to the project root directory
|
||||
wd, _ := os.Getwd()
|
||||
for !strings.HasSuffix(wd, "authelia") {
|
||||
wd = filepath.Dir(wd)
|
||||
}
|
||||
cmd.Dir = wd
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CommandWithStdout create a command forwarding stdout and stderr to the OS streams
|
||||
func CommandWithStdout(name string, args ...string) *exec.Cmd {
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd := Command(name, args...)
|
||||
if log.GetLevel() > log.InfoLevel {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
|
3915
package-lock.json
generated
3915
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
43
package.json
43
package.json
|
@ -1,43 +0,0 @@
|
|||
{
|
||||
"name": "authelia",
|
||||
"version": "3.16.3",
|
||||
"description": "2FA Single Sign-On server for nginx using LDAP, TOTP and U2F",
|
||||
"engines": {
|
||||
"node": ">=8.0.0 <10.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/clems4ever/authelia"
|
||||
},
|
||||
"author": "Clement Michaud <clement.michaud34@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/clems4ever/authelia/issues"
|
||||
},
|
||||
"apidoc": {
|
||||
"title": "Authelia API documentation"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/node-fetch": "^2.1.4",
|
||||
"@types/query-string": "^5.1.0",
|
||||
"@types/request": "^2.0.5",
|
||||
"@types/request-promise": "^4.1.38",
|
||||
"@types/selenium-webdriver": "^3.0.16",
|
||||
"@types/speakeasy": "^2.0.2",
|
||||
"chromedriver": "^77.0.0",
|
||||
"ejs": "^2.6.2",
|
||||
"mocha": "^6.1.4",
|
||||
"node-fetch": "^2.3.0",
|
||||
"query-string": "^6.0.0",
|
||||
"readable-stream": "^2.3.3",
|
||||
"request": "^2.88.0",
|
||||
"request-promise": "^4.2.2",
|
||||
"selenium-webdriver": "^4.0.0-alpha.4",
|
||||
"speakeasy": "^2.0.0",
|
||||
"ts-node": "^6.0.1",
|
||||
"tslint": "^5.2.0",
|
||||
"typescript": "^2.9.2"
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
var { setup } = require(`../test/suites/${process.argv[2]}/environment`);
|
||||
|
||||
(async function() {
|
||||
try {
|
||||
await setup();
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
})()
|
|
@ -1,10 +0,0 @@
|
|||
var { teardown } = require(`../test/suites/${process.argv[2]}/environment`);
|
||||
|
||||
(async function() {
|
||||
try {
|
||||
await teardown();
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
})()
|
|
@ -1,13 +0,0 @@
|
|||
const { lstatSync, readdirSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
|
||||
const isDirectory = source => lstatSync(source).isDirectory()
|
||||
const getDirectories = source =>
|
||||
readdirSync(source)
|
||||
.map(name => join(source, name))
|
||||
.filter(isDirectory)
|
||||
.map(x => x.split('/').slice(-1)[0])
|
||||
|
||||
module.exports = function() {
|
||||
return getDirectories('test/suites/');
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
var spawn = require('child_process').spawn;
|
||||
|
||||
function exec(cmd) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const command = spawn(cmd, {shell: true, env: process.env});
|
||||
command.stdout.pipe(process.stdout);
|
||||
command.stderr.pipe(process.stderr);
|
||||
command.on('exit', function(statusCode) {
|
||||
if (statusCode != 0) {
|
||||
reject(new Error('Command \'' + cmd + '\' has exited with status ' + statusCode + '.'));
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { exec }
|
|
@ -1,7 +0,0 @@
|
|||
import SeleniumWebdriver, { WebDriver, Locator } from "selenium-webdriver";
|
||||
|
||||
export default async function(driver: WebDriver, locator: Locator, timeout: number = 5000) {
|
||||
const el = await driver.wait(
|
||||
SeleniumWebdriver.until.elementLocated(locator), timeout);
|
||||
await el.click();
|
||||
};
|
|
@ -1,8 +0,0 @@
|
|||
import SeleniumWebdriver, { WebDriver } from "selenium-webdriver";
|
||||
|
||||
export default async function(driver: WebDriver, linkText: string, timeout: number = 5000) {
|
||||
const element = await driver.wait(
|
||||
SeleniumWebdriver.until.elementLocated(
|
||||
SeleniumWebdriver.By.linkText(linkText)), timeout);
|
||||
await element.click();
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
import SeleniumWebdriver, { WebDriver } from "selenium-webdriver";
|
||||
|
||||
export default async function(driver: WebDriver, fieldName: string, text: string, timeout: number = 5000) {
|
||||
const element = await driver.wait(
|
||||
SeleniumWebdriver.until.elementLocated(
|
||||
SeleniumWebdriver.By.name(fieldName)), timeout)
|
||||
|
||||
await element.sendKeys(text);
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
import SeleniumWebdriver, { WebDriver } from "selenium-webdriver";
|
||||
|
||||
export default async function(
|
||||
driver: WebDriver,
|
||||
username: string,
|
||||
password: string,
|
||||
keepMeLoggedIn: boolean = false,
|
||||
timeout: number = 5000) {
|
||||
|
||||
await driver.wait(SeleniumWebdriver.until.elementLocated(SeleniumWebdriver.By.id("username")), timeout)
|
||||
await driver.findElement(SeleniumWebdriver.By.id("username")).sendKeys(username);
|
||||
await driver.findElement(SeleniumWebdriver.By.id("password")).sendKeys(password);
|
||||
if (keepMeLoggedIn) {
|
||||
await driver.findElement(SeleniumWebdriver.By.id("remember-checkbox")).click();
|
||||
}
|
||||
await driver.findElement(SeleniumWebdriver.By.tagName("button")).click();
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
import FillLoginPageAndClick from "./FillLoginPageAndClick";
|
||||
import ValidateTotp from "./ValidateTotp";
|
||||
import { WebDriver } from "selenium-webdriver";
|
||||
import VisitPageAndWaitUrlIs from "./behaviors/VisitPageAndWaitUrlIs";
|
||||
|
||||
// Validate the two factors!
|
||||
export default async function(driver: WebDriver, user: string, secret: string, url: string, timeout: number = 5000) {
|
||||
await VisitPageAndWaitUrlIs(driver, `https://login.example.com:8080/#/?rd=${url}`, timeout);
|
||||
await FillLoginPageAndClick(driver, user, 'password', false, timeout);
|
||||
await ValidateTotp(driver, secret, timeout);
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
import Bluebird = require("bluebird");
|
||||
import Fs = require("fs");
|
||||
import Request = require("request-promise");
|
||||
|
||||
export async function GetLinkFromFile() {
|
||||
const data = await Bluebird.promisify(Fs.readFile)(
|
||||
"/tmp/authelia/notification.txt")
|
||||
const regexp = new RegExp(/Link: (.+)/);
|
||||
const match = regexp.exec(data.toLocaleString());
|
||||
if (match == null) {
|
||||
throw new Error('No match');
|
||||
}
|
||||
return match[1];
|
||||
};
|
||||
|
||||
export async function GetLinkFromEmail() {
|
||||
const data = await Request({
|
||||
method: "GET",
|
||||
uri: "https://mail.example.com:8080/messages",
|
||||
json: true,
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
const messageId = data[data.length - 1].id;
|
||||
const data2 = await Request({
|
||||
method: "GET",
|
||||
rejectUnauthorized: false,
|
||||
uri: `https://mail.example.com:8080/messages/${messageId}.html`,
|
||||
});
|
||||
|
||||
const regexp = new RegExp(/<a href="(.+)" class="button">.*<\/a>/);
|
||||
const match = regexp.exec(data2);
|
||||
if (match == null) {
|
||||
throw new Error('No match');
|
||||
}
|
||||
return match[1];
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
import RegisterTotp from './RegisterTotp';
|
||||
import LoginAs from './LoginAs';
|
||||
import { WebDriver } from 'selenium-webdriver';
|
||||
import VerifyIsSecondFactorStage from './assertions/VerifyIsSecondFactorStage';
|
||||
|
||||
export default async function(driver: WebDriver, user: string, password: string, email: boolean = false, timeout: number = 5000) {
|
||||
await LoginAs(driver, user, password, undefined, timeout);
|
||||
await VerifyIsSecondFactorStage(driver, timeout);
|
||||
return RegisterTotp(driver, email, timeout);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user