refactor(suites): replace selenium with go-rod (#2534)

* refactor(suites): replace selenium with go-rod

This change replaces [tebeka/selenium](https://github.com/tebeka/selenium) with [go-rod](https://github.com/go-rod/rod).

We no longer have a chromedriver/external driver dependency to utilise Selenium as we instead utilise the Chrome Dev Protocol to communicate with the browser.

Rod [documents](https://go-rod.github.io/#/why-rod) benefits of choosing the library as opposed to the available alternatives.
This commit is contained in:
Amir Zarrinkafsh 2021-11-06 00:14:42 +11:00 committed by GitHub
parent 0e8ff3bde9
commit 83488d52a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 1138 additions and 1026 deletions

2
go.mod
View File

@ -13,6 +13,7 @@ require (
github.com/fasthttp/router v1.4.4
github.com/fasthttp/session/v2 v2.4.4
github.com/go-ldap/ldap/v3 v3.4.1
github.com/go-rod/rod v0.101.8
github.com/go-sql-driver/mysql v1.6.0
github.com/golang-jwt/jwt/v4 v4.1.0
github.com/golang/mock v1.6.0
@ -30,7 +31,6 @@ require (
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.2.1
github.com/stretchr/testify v1.7.0
github.com/tebeka/selenium v0.9.9
github.com/tstranex/u2f v1.0.0
github.com/valyala/fasthttp v1.31.0
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b // indirect

23
go.sum
View File

@ -43,10 +43,7 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/BurntSushi/xgbutil v0.0.0-20160919175755-f7c97cef3b4e h1:4ZrkT/RzpnROylmoQL57iVUL57wGKTR5O6KpVnbm2tA=
github.com/BurntSushi/xgbutil v0.0.0-20160919175755-f7c97cef3b4e/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
@ -89,8 +86,6 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
@ -119,8 +114,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmatcuk/doublestar/v2 v2.0.3/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
@ -315,6 +308,8 @@ github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7
github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8=
github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
github.com/go-rod/rod v0.101.8 h1:oV0O97uwjkCVyAP0hD6K6bBE8FUMIjs0dtF7l6kEBsU=
github.com/go-rod/rod v0.101.8/go.mod h1:N/zlT53CfSpq74nb6rOR0K8UF0SPUPBmzBnArrms+mY=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@ -646,10 +641,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github/v27 v27.0.4/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0=
github.com/google/go-jsonnet v0.16.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw=
github.com/google/go-jsonnet v0.17.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -1297,8 +1290,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tebeka/selenium v0.9.9 h1:cNziB+etNgyH/7KlNI7RMC1ua5aH1+5wUlFQyzeMh+w=
github.com/tebeka/selenium v0.9.9/go.mod h1:5Fr8+pUvU6B1OiPfkdCKdXZyr5znvVkxuPd0NOdZCQc=
github.com/tidwall/gjson v1.11.0 h1:C16pk7tQNiH6VlCrtIXL1w8GaOsi1X3W8KDkE1BuYd4=
github.com/tidwall/gjson v1.11.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@ -1342,6 +1333,16 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
github.com/ysmood/goob v0.3.0 h1:XZ51cZJ4W3WCoCiUktixzMIQF86W7G5VFL4QQ/Q2uS0=
github.com/ysmood/goob v0.3.0/go.mod h1:S3lq113Y91y1UBf1wj1pFOxeahvfKkCk6mTWTWbDdWs=
github.com/ysmood/got v0.15.1 h1:X5jAbMyBf5yeezuFMp9HaMGXZWMSqIQcUlAHI+kJmUs=
github.com/ysmood/got v0.15.1/go.mod h1:pE1l4LOwOBhQg6A/8IAatkGp7uZjnalzrZolnlhhMgY=
github.com/ysmood/gotrace v0.2.2 h1:006KHGRThSRf8lwh4EyhNmuuq/l+Ygs+JqojkhEG1/E=
github.com/ysmood/gotrace v0.2.2/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM=
github.com/ysmood/gson v0.6.4 h1:Yb6tosv6bk59HqjZu2/7o4BFherpYEMkDkXmlhgryZ4=
github.com/ysmood/gson v0.6.4/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=
github.com/ysmood/leakless v0.7.0 h1:XCGdaPExyoreoQd+H5qgxM3ReNbSPFsEXpSKwbXbwQw=
github.com/ysmood/leakless v0.7.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

View File

@ -1,17 +1,17 @@
package suites
import (
"context"
"fmt"
"testing"
"github.com/go-rod/rod"
"github.com/stretchr/testify/require"
)
func (wds *WebDriverSession) doChangeMethod(ctx context.Context, t *testing.T, method string) {
err := wds.WaitElementLocatedByID(ctx, t, "methods-button").Click()
func (rs *RodSession) doChangeMethod(t *testing.T, page *rod.Page, method string) {
err := rs.WaitElementLocatedByCSSSelector(t, page, "methods-button").Click("left")
require.NoError(t, err)
wds.WaitElementLocatedByID(ctx, t, "methods-dialog")
err = wds.WaitElementLocatedByID(ctx, t, fmt.Sprintf("%s-option", method)).Click()
rs.WaitElementLocatedByCSSSelector(t, page, "methods-dialog")
err = rs.WaitElementLocatedByCSSSelector(t, page, fmt.Sprintf("%s-option", method)).Click("left")
require.NoError(t, err)
}

View File

@ -1,44 +1,44 @@
package suites
import (
"context"
"testing"
"time"
"github.com/go-rod/rod"
"github.com/stretchr/testify/require"
)
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)
func (rs *RodSession) doFillLoginPageAndClick(t *testing.T, page *rod.Page, username, password string, keepMeLoggedIn bool) {
usernameElement := rs.WaitElementLocatedByCSSSelector(t, page, "username-textfield")
err := usernameElement.Input(username)
require.NoError(t, err)
passwordElement := wds.WaitElementLocatedByID(ctx, t, "password-textfield")
err = passwordElement.SendKeys(password)
passwordElement := rs.WaitElementLocatedByCSSSelector(t, page, "password-textfield")
err = passwordElement.Input(password)
require.NoError(t, err)
if keepMeLoggedIn {
keepMeLoggedInElement := wds.WaitElementLocatedByID(ctx, t, "remember-checkbox")
err = keepMeLoggedInElement.Click()
keepMeLoggedInElement := rs.WaitElementLocatedByCSSSelector(t, page, "remember-checkbox")
err = keepMeLoggedInElement.Click("left")
require.NoError(t, err)
}
buttonElement := wds.WaitElementLocatedByID(ctx, t, "sign-in-button")
err = buttonElement.Click()
buttonElement := rs.WaitElementLocatedByCSSSelector(t, page, "sign-in-button")
err = buttonElement.Click("left")
require.NoError(t, err)
}
// 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 (rs *RodSession) doLoginOneFactor(t *testing.T, page *rod.Page, username, password string, keepMeLoggedIn bool, targetURL string) {
rs.doVisitLoginPage(t, page, targetURL)
rs.doFillLoginPageAndClick(t, page, username, password, keepMeLoggedIn)
}
// 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 (rs *RodSession) doLoginTwoFactor(t *testing.T, page *rod.Page, username, password string, keepMeLoggedIn bool, otpSecret, targetURL string) {
rs.doLoginOneFactor(t, page, username, password, keepMeLoggedIn, targetURL)
rs.verifyIsSecondFactorPage(t, page)
rs.doValidateTOTP(t, page, otpSecret)
// timeout when targetURL is not defined to prevent a show stopping redirect when visiting a protected domain
if targetURL == "" {
time.Sleep(1 * time.Second)
@ -46,20 +46,20 @@ func (wds *WebDriverSession) doLoginTwoFactor(ctx context.Context, t *testing.T,
}
// 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, GetLoginBaseURL())
wds.verifyIsSecondFactorPage(ctx, t)
func (rs *RodSession) doLoginAndRegisterTOTP(t *testing.T, page *rod.Page, username, password string, keepMeLoggedIn bool) string {
rs.doLoginOneFactor(t, page, username, password, keepMeLoggedIn, "")
secret := rs.doRegisterTOTP(t, page)
rs.doVisit(t, page, GetLoginBaseURL())
rs.verifyIsSecondFactorPage(t, page)
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 { //nolint:unparam
func (rs *RodSession) doRegisterAndLogin2FA(t *testing.T, page *rod.Page, username, password string, keepMeLoggedIn bool, targetURL string) string { //nolint:unparam
// Register TOTP secret and logout.
secret := wds.doRegisterThenLogout(ctx, t, username, password)
wds.doLoginTwoFactor(ctx, t, username, password, keepMeLoggedIn, secret, targetURL)
secret := rs.doRegisterThenLogout(t, page, username, password)
rs.doLoginTwoFactor(t, page, username, password, keepMeLoggedIn, secret, targetURL)
return secret
}

View File

@ -1,25 +1,28 @@
package suites
import (
"context"
"fmt"
"net/url"
"testing"
"github.com/go-rod/rod"
)
func (wds *WebDriverSession) doLogout(ctx context.Context, t *testing.T) {
wds.doVisit(t, fmt.Sprintf("%s%s", GetLoginBaseURL(), "/logout"))
wds.verifyIsFirstFactorPage(ctx, t)
func (rs *RodSession) doLogout(t *testing.T, page *rod.Page) {
rs.doVisit(t, page, fmt.Sprintf("%s%s", GetLoginBaseURL(), "/logout"))
rs.verifyIsFirstFactorPage(t, page)
}
func (wds *WebDriverSession) doLogoutWithRedirect(ctx context.Context, t *testing.T, targetURL string, firstFactor bool) {
wds.doVisit(t, fmt.Sprintf("%s%s%s", GetLoginBaseURL(), "/logout?rd=", url.QueryEscape(targetURL)))
func (rs *RodSession) doLogoutWithRedirect(t *testing.T, page *rod.Page, targetURL string, firstFactor bool) {
rs.doVisit(t, page, fmt.Sprintf("%s%s%s", GetLoginBaseURL(), "/logout?rd=", url.QueryEscape(targetURL)))
if firstFactor {
wds.verifyIsFirstFactorPage(ctx, t)
rs.verifyIsFirstFactorPage(t, page)
return
}
wds.verifyURLIs(ctx, t, targetURL)
page.MustElementR("h1", "Public resource")
rs.verifyURLIs(t, page, targetURL)
}

View File

@ -1,13 +1,14 @@
package suites
import (
"context"
"testing"
"github.com/go-rod/rod"
)
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)
func (rs *RodSession) doRegisterThenLogout(t *testing.T, page *rod.Page, username, password string) string {
secret := rs.doLoginAndRegisterTOTP(t, page, username, password, false)
rs.doLogout(t, page)
return secret
}

View File

@ -1,52 +1,60 @@
package suites
import (
"context"
"testing"
"time"
"github.com/go-rod/rod"
"github.com/stretchr/testify/require"
)
func (wds *WebDriverSession) doInitiatePasswordReset(ctx context.Context, t *testing.T, username string) {
err := wds.WaitElementLocatedByID(ctx, t, "reset-password-button").Click()
func (rs *RodSession) doInitiatePasswordReset(t *testing.T, page *rod.Page, username string) {
err := rs.WaitElementLocatedByCSSSelector(t, page, "reset-password-button").Click("left")
require.NoError(t, err)
// Fill in username
err = wds.WaitElementLocatedByID(ctx, t, "username-textfield").SendKeys(username)
err = rs.WaitElementLocatedByCSSSelector(t, page, "username-textfield").Input(username)
require.NoError(t, err)
// And click on the reset button
err = wds.WaitElementLocatedByID(ctx, t, "reset-button").Click()
err = rs.WaitElementLocatedByCSSSelector(t, page, "reset-button").Click("left")
require.NoError(t, err)
}
func (wds *WebDriverSession) doCompletePasswordReset(ctx context.Context, t *testing.T, newPassword1, newPassword2 string) {
func (rs *RodSession) doCompletePasswordReset(t *testing.T, page *rod.Page, newPassword1, newPassword2 string) {
link := doGetLinkFromLastMail(t)
wds.doVisit(t, link)
rs.doVisit(t, page, link)
err := wds.WaitElementLocatedByID(ctx, t, "password1-textfield").SendKeys(newPassword1)
time.Sleep(1 * time.Second)
err := rs.WaitElementLocatedByCSSSelector(t, page, "password1-textfield").Input(newPassword1)
require.NoError(t, err)
err = wds.WaitElementLocatedByID(ctx, t, "password2-textfield").SendKeys(newPassword2)
time.Sleep(1 * time.Second)
err = rs.WaitElementLocatedByCSSSelector(t, page, "password2-textfield").Input(newPassword2)
require.NoError(t, err)
err = wds.WaitElementLocatedByID(ctx, t, "reset-button").Click()
err = rs.WaitElementLocatedByCSSSelector(t, page, "reset-button").Click("left")
require.NoError(t, err)
}
func (wds *WebDriverSession) doSuccessfullyCompletePasswordReset(ctx context.Context, t *testing.T, newPassword1, newPassword2 string) {
wds.doCompletePasswordReset(ctx, t, newPassword1, newPassword2)
wds.verifyIsFirstFactorPage(ctx, t)
func (rs *RodSession) doSuccessfullyCompletePasswordReset(t *testing.T, page *rod.Page, newPassword1, newPassword2 string) {
rs.doCompletePasswordReset(t, page, newPassword1, newPassword2)
rs.verifyIsFirstFactorPage(t, page)
}
func (wds *WebDriverSession) doUnsuccessfulPasswordReset(ctx context.Context, t *testing.T, newPassword1, newPassword2 string) {
wds.doCompletePasswordReset(ctx, t, newPassword1, newPassword2)
func (rs *RodSession) doUnsuccessfulPasswordReset(t *testing.T, page *rod.Page, newPassword1, newPassword2 string) {
rs.doCompletePasswordReset(t, page, newPassword1, newPassword2)
rs.verifyNotificationDisplayed(t, page, "Your supplied password does not meet the password policy requirements.")
}
func (wds *WebDriverSession) doResetPassword(ctx context.Context, t *testing.T, username, newPassword1, newPassword2 string, unsuccessful bool) {
wds.doInitiatePasswordReset(ctx, t, username)
func (rs *RodSession) doResetPassword(t *testing.T, page *rod.Page, username, newPassword1, newPassword2 string, unsuccessful bool) {
rs.doInitiatePasswordReset(t, page, username)
// then wait for the "email sent notification"
wds.verifyMailNotificationDisplayed(ctx, t)
rs.verifyMailNotificationDisplayed(t, page)
if unsuccessful {
wds.doUnsuccessfulPasswordReset(ctx, t, newPassword1, newPassword2)
rs.doUnsuccessfulPasswordReset(t, page, newPassword1, newPassword2)
} else {
wds.doSuccessfullyCompletePasswordReset(ctx, t, newPassword1, newPassword2)
rs.doSuccessfullyCompletePasswordReset(t, page, newPassword1, newPassword2)
}
}

View File

@ -1,43 +1,42 @@
package suites
import (
"context"
"strings"
"testing"
"time"
"github.com/go-rod/rod"
"github.com/pquerna/otp/totp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func (wds *WebDriverSession) doRegisterTOTP(ctx context.Context, t *testing.T) string {
err := wds.WaitElementLocatedByID(ctx, t, "register-link").Click()
func (rs *RodSession) doRegisterTOTP(t *testing.T, page *rod.Page) string {
err := rs.WaitElementLocatedByCSSSelector(t, page, "register-link").Click("left")
require.NoError(t, err)
wds.verifyMailNotificationDisplayed(ctx, t)
rs.verifyMailNotificationDisplayed(t, page)
link := doGetLinkFromLastMail(t)
wds.doVisit(t, link)
secretURL, err := wds.WaitElementLocatedByID(ctx, t, "secret-url").GetAttribute("value")
rs.doVisit(t, page, link)
secretURL, err := page.MustElement("#secret-url").Attribute("value")
assert.NoError(t, err)
secret := secretURL[strings.LastIndex(secretURL, "=")+1:]
secret := (*secretURL)[strings.LastIndex(*secretURL, "=")+1:]
assert.NotEqual(t, "", secret)
assert.NotNil(t, secret)
return secret
}
func (wds *WebDriverSession) doEnterOTP(ctx context.Context, t *testing.T, code string) {
inputs := wds.WaitElementsLocatedByCSSSelector(ctx, t, "#otp-input input")
func (rs *RodSession) doEnterOTP(t *testing.T, page *rod.Page, code string) {
inputs := rs.WaitElementsLocatedByCSSSelector(t, page, "otp-input input")
for i := 0; i < 6; i++ {
err := inputs[i].SendKeys(string(code[i]))
require.NoError(t, err)
_ = inputs[i].Input(string(code[i]))
}
}
func (wds *WebDriverSession) doValidateTOTP(ctx context.Context, t *testing.T, secret string) {
func (rs *RodSession) doValidateTOTP(t *testing.T, page *rod.Page, secret string) {
code, err := totp.GenerateCode(secret, time.Now())
assert.NoError(t, err)
wds.doEnterOTP(ctx, t, code)
rs.doEnterOTP(t, page, code)
}

View File

@ -1,28 +1,36 @@
package suites
import (
"context"
"fmt"
"testing"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/proto"
"github.com/stretchr/testify/assert"
)
func (wds *WebDriverSession) doVisit(t *testing.T, url string) {
err := wds.WebDriver.Get(url)
func (rs *RodSession) doCreateTab(t *testing.T, url string) *rod.Page {
p, err := rs.WebDriver.MustIncognito().Page(proto.TargetCreateTarget{URL: url})
assert.NoError(t, err)
return p
}
func (rs *RodSession) doVisit(t *testing.T, page *rod.Page, url string) {
err := page.Navigate(url)
assert.NoError(t, err)
}
func (wds *WebDriverSession) doVisitAndVerifyOneFactorStep(ctx context.Context, t *testing.T, url string) {
wds.doVisit(t, url)
wds.verifyIsFirstFactorPage(ctx, t)
func (rs *RodSession) doVisitAndVerifyOneFactorStep(t *testing.T, page *rod.Page, url string) {
rs.doVisit(t, page, url)
rs.verifyIsFirstFactorPage(t, page)
}
func (wds *WebDriverSession) doVisitLoginPage(ctx context.Context, t *testing.T, targetURL string) {
func (rs *RodSession) doVisitLoginPage(t *testing.T, page *rod.Page, targetURL string) {
suffix := ""
if targetURL != "" {
suffix = fmt.Sprintf("?rd=%s", targetURL)
}
wds.doVisitAndVerifyOneFactorStep(ctx, t, fmt.Sprintf("%s/%s", GetLoginBaseURL(), suffix))
rs.doVisitAndVerifyOneFactorStep(t, page, fmt.Sprintf("%s/%s", GetLoginBaseURL(), suffix))
}

View File

@ -51,7 +51,6 @@ var DuoBaseURL = "https://duo.example.com"
var AutheliaBaseURL = "https://authelia.example.com:9091"
const stringTrue = "true"
const defaultChromeDriverPort = "4444"
const testUsername = "john"
const testPassword = "password"

View File

@ -1 +1 @@
-R '^web/' -r '(\.go$|go\.mod|\.sh|\.yml)' -s /resources/run-backend-dev.sh
-R '^web/' -R 'users.yml' -r '(\.go$|go\.mod|\.sh|\.yml)' -s /resources/run-backend-dev.sh

View File

@ -4,11 +4,15 @@ global
log stdout format raw local0 debug
defaults
default-server init-addr none
mode http
log global
option httplog
option forwardfor
resolvers docker
nameserver ip 127.0.0.11:53
frontend fe_api
bind *:8081 ssl crt /usr/local/etc/haproxy/haproxy.pem
@ -63,13 +67,13 @@ backend be_auth_request
listen be_auth_request_proxy
mode http
bind 127.0.0.1:8085
server authelia-backend authelia-backend:9091 ssl verify none
server authelia-backend authelia-backend:9091 resolvers docker ssl verify none
backend be_authelia
server authelia-backend authelia-backend:9091 ssl verify none
server authelia-backend authelia-backend:9091 resolvers docker ssl verify none
backend fe_authelia
server authelia-frontend authelia-frontend:3000
server authelia-frontend authelia-frontend:3000 resolvers docker
backend be_httpbin
acl remote_user_exist var(req.auth_response_header.remote_user) -m found
@ -81,10 +85,10 @@ backend be_httpbin
http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist
http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist
server httpbin-backend httpbin:8000
server httpbin-backend httpbin:8000 resolvers docker
backend be_mail
server smtp-backend smtp:1080
server smtp-backend smtp:1080 resolvers docker
backend be_protected
server nginx-backend nginx-backend:80
server nginx-backend nginx-backend:80 resolvers docker

View File

@ -5,36 +5,34 @@ import (
"log"
"time"
"github.com/tebeka/selenium"
"github.com/authelia/authelia/v4/internal/utils"
)
type AvailableMethodsScenario struct {
*SeleniumSuite
*RodSuite
methods []string
}
func NewAvailableMethodsScenario(methods []string) *AvailableMethodsScenario {
return &AvailableMethodsScenario{
SeleniumSuite: new(SeleniumSuite),
methods: methods,
RodSuite: new(RodSuite),
methods: methods,
}
}
func (s *AvailableMethodsScenario) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.SeleniumSuite.WebDriverSession = wds
s.RodSession = browser
}
func (s *AvailableMethodsScenario) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -42,26 +40,30 @@ func (s *AvailableMethodsScenario) TearDownSuite() {
}
func (s *AvailableMethodsScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
func (s *AvailableMethodsScenario) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *AvailableMethodsScenario) TestShouldCheckAvailableMethods() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
methodsButton := s.WaitElementLocatedByID(ctx, s.T(), "methods-button")
err := methodsButton.Click()
methodsButton := s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "methods-button")
err := methodsButton.Click("left")
s.Assert().NoError(err)
methodsDialog := s.WaitElementLocatedByID(ctx, s.T(), "methods-dialog")
options, err := methodsDialog.FindElements(selenium.ByClassName, "method-option")
methodsDialog := s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "methods-dialog")
options, err := methodsDialog.Elements(".method-option")
s.Assert().NoError(err)
s.Assert().Len(options, len(s.methods))

View File

@ -11,27 +11,27 @@ import (
)
type BypassPolicyScenario struct {
*SeleniumSuite
*RodSuite
}
func NewBypassPolicyScenario() *BypassPolicyScenario {
return &BypassPolicyScenario{
SeleniumSuite: new(SeleniumSuite),
RodSuite: new(RodSuite),
}
}
func (s *BypassPolicyScenario) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *BypassPolicyScenario) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -39,23 +39,27 @@ func (s *BypassPolicyScenario) TearDownSuite() {
}
func (s *BypassPolicyScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
func (s *BypassPolicyScenario) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *BypassPolicyScenario) TestShouldAccessPublicResource() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doVisit(s.T(), AdminBaseURL)
s.verifyIsFirstFactorPage(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), AdminBaseURL)
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
s.doVisit(s.T(), fmt.Sprintf("%s/secret.html", PublicBaseURL))
s.verifySecretAuthorized(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/secret.html", PublicBaseURL))
s.verifySecretAuthorized(s.T(), s.Context(ctx))
}
func TestBypassPolicyScenario(t *testing.T) {

View File

@ -10,33 +10,31 @@ import (
"time"
mapset "github.com/deckarep/golang-set"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/tebeka/selenium"
)
type CustomHeadersScenario struct {
*SeleniumSuite
*RodSuite
}
func NewCustomHeadersScenario() *CustomHeadersScenario {
return &CustomHeadersScenario{
SeleniumSuite: new(SeleniumSuite),
RodSuite: new(RodSuite),
}
}
func (s *CustomHeadersScenario) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *CustomHeadersScenario) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -44,23 +42,26 @@ func (s *CustomHeadersScenario) TearDownSuite() {
}
func (s *CustomHeadersScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
func (s *CustomHeadersScenario) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *CustomHeadersScenario) TestShouldNotForwardCustomHeaderForUnauthenticatedUser() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doVisit(s.T(), fmt.Sprintf("%s/headers", PublicBaseURL))
s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/headers", PublicBaseURL))
body, err := s.WebDriver().FindElement(selenium.ByTagName, "body")
body, err := s.Context(ctx).Element("body")
s.Assert().NoError(err)
s.WaitElementTextContains(ctx, s.T(), body, "\"Host\"")
b, err := body.Text()
s.Assert().NoError(err)
@ -82,46 +83,46 @@ type HeadersPayload struct {
}
func (s *CustomHeadersScenario) TestShouldForwardCustomHeaderForAuthenticatedUser() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
expectedGroups := mapset.NewSetWith("dev", "admins")
targetURL := fmt.Sprintf("%s/headers", PublicBaseURL)
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, targetURL)
s.verifyURLIs(ctx, s.T(), targetURL)
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, targetURL)
s.verifyIsPublic(s.T(), s.Context(ctx))
err := s.Wait(ctx, func(d selenium.WebDriver) (bool, error) {
body, err := s.WebDriver().FindElement(selenium.ByTagName, "body")
if err != nil {
return false, err
}
body, err := s.Context(ctx).Element("body")
s.Assert().NoError(err)
s.Assert().NotNil(body)
if body == nil {
return false, nil
}
content, err := body.Text()
s.Assert().NoError(err)
s.Assert().NotNil(content)
content, err := body.Text()
if err != nil {
return false, err
}
payload := HeadersPayload{}
if err := json.Unmarshal([]byte(content), &payload); err != nil {
log.Panic(err)
}
payload := HeadersPayload{}
if err := json.Unmarshal([]byte(content), &payload); err != nil {
return false, err
}
groups := strings.Split(payload.Headers.ForwardedGroups, ",")
actualGroups := mapset.NewSet()
groups := strings.Split(payload.Headers.ForwardedGroups, ",")
actualGroups := mapset.NewSet()
for _, group := range groups {
actualGroups.Add(group)
}
for _, group := range groups {
actualGroups.Add(group)
}
return strings.Contains(payload.Headers.ForwardedUser, "john") && expectedGroups.Equal(actualGroups) &&
strings.Contains(payload.Headers.ForwardedName, "John Doe") && strings.Contains(payload.Headers.ForwardedEmail, "john.doe@authelia.com"), nil
})
if strings.Contains(payload.Headers.ForwardedUser, "john") && expectedGroups.Equal(actualGroups) &&
strings.Contains(payload.Headers.ForwardedName, "John Doe") && strings.Contains(payload.Headers.ForwardedEmail, "john.doe@authelia.com") {
err = nil
} else {
err = fmt.Errorf("headers do not include user information")
}
require.NoError(s.T(), err)
s.Require().NoError(err)
}
func TestCustomHeadersScenario(t *testing.T) {

View File

@ -11,57 +11,62 @@ import (
)
type DefaultRedirectionURLScenario struct {
*SeleniumSuite
*RodSuite
secret string
}
func NewDefaultRedirectionURLScenario() *DefaultRedirectionURLScenario {
return &DefaultRedirectionURLScenario{
SeleniumSuite: new(SeleniumSuite),
RodSuite: new(RodSuite),
}
}
func (drus *DefaultRedirectionURLScenario) SetupSuite() {
wds, err := StartWebDriver()
func (s *DefaultRedirectionURLScenario) SetupSuite() {
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
drus.WebDriverSession = wds
s.RodSession = browser
}
func (s *DefaultRedirectionURLScenario) TearDownSuite() {
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
}
}
func (s *DefaultRedirectionURLScenario) SetupTest() {
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
func (s *DefaultRedirectionURLScenario) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *DefaultRedirectionURLScenario) TestUserIsRedirectedToDefaultURL() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
drus.secret = drus.doRegisterAndLogin2FA(ctx, drus.T(), "john", "password", false, targetURL)
drus.verifySecretAuthorized(ctx, drus.T())
}
func (drus *DefaultRedirectionURLScenario) TearDownSuite() {
err := drus.WebDriverSession.Stop()
s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
s.secret = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, targetURL)
s.verifySecretAuthorized(s.T(), s.Context(ctx))
s.doLogout(s.T(), s.Context(ctx))
if err != nil {
log.Fatal(err)
}
}
func (drus *DefaultRedirectionURLScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
drus.doLogout(ctx, drus.T())
drus.doVisit(drus.T(), HomeBaseURL)
drus.verifyIsHome(ctx, drus.T())
}
func (drus *DefaultRedirectionURLScenario) TestUserIsRedirectedToDefaultURL() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
drus.doLoginTwoFactor(ctx, drus.T(), "john", "password", false, drus.secret, "")
drus.verifyURLIs(ctx, drus.T(), HomeBaseURL+"/")
s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, s.secret, "")
s.verifyIsHome(s.T(), s.Page)
}
func TestShouldRunDefaultRedirectionURLScenario(t *testing.T) {

View File

@ -11,35 +11,42 @@ import (
)
type InactivityScenario struct {
*SeleniumSuite
*RodSuite
secret string
}
func NewInactivityScenario() *InactivityScenario {
return &InactivityScenario{
SeleniumSuite: new(SeleniumSuite),
RodSuite: new(RodSuite),
}
}
func (s *InactivityScenario) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
s.collectCoverage(s.Page)
s.MustClose()
}()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
s.secret = s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, targetURL)
s.verifySecretAuthorized(ctx, s.T())
s.secret = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, targetURL)
s.verifySecretAuthorized(s.T(), s.Context(ctx))
}
func (s *InactivityScenario) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -47,71 +54,78 @@ func (s *InactivityScenario) TearDownSuite() {
}
func (s *InactivityScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
func (s *InactivityScenario) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *InactivityScenario) TestShouldRequireReauthenticationAfterInactivityPeriod() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
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())
s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, s.secret, "")
s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
s.verifyIsHome(s.T(), s.Context(ctx))
time.Sleep(6 * time.Second)
s.doVisit(s.T(), targetURL)
s.verifyIsFirstFactorPage(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), targetURL)
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
}
func (s *InactivityScenario) TestShouldRequireReauthenticationAfterCookieExpiration() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, s.secret, "")
s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, s.secret, "")
for i := 0; i < 3; i++ {
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
s.verifyIsHome(s.T(), s.Context(ctx))
time.Sleep(2 * time.Second)
s.doVisit(s.T(), targetURL)
s.verifySecretAuthorized(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), targetURL)
s.verifySecretAuthorized(s.T(), s.Context(ctx))
}
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())
s.doVisit(s.T(), s.Context(ctx), targetURL)
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
}
func (s *InactivityScenario) TestShouldDisableCookieExpirationAndInactivity() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
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())
s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", true, s.secret, "")
s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
s.verifyIsHome(s.T(), s.Context(ctx))
time.Sleep(10 * time.Second)
s.doVisit(s.T(), targetURL)
s.verifySecretAuthorized(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), targetURL)
s.verifySecretAuthorized(s.T(), s.Context(ctx))
}
func TestInactivityScenario(t *testing.T) {

View File

@ -12,33 +12,40 @@ import (
)
type OIDCScenario struct {
*SeleniumSuite
*RodSuite
secret string
}
func NewOIDCScenario() *OIDCScenario {
return &OIDCScenario{
SeleniumSuite: new(SeleniumSuite),
RodSuite: new(RodSuite),
}
}
func (s *OIDCScenario) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
s.secret = s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, AdminBaseURL)
s.collectCoverage(s.Page)
s.MustClose()
}()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.secret = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, AdminBaseURL)
}
func (s *OIDCScenario) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -47,65 +54,73 @@ func (s *OIDCScenario) TearDownSuite() {
func (s *OIDCScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doVisit(s.T(), fmt.Sprintf("%s/logout", OIDCBaseURL))
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
s.Page = s.doCreateTab(s.T(), fmt.Sprintf("%s/logout", OIDCBaseURL))
s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
s.verifyIsHome(s.T(), s.Context(ctx))
}
func (s *OIDCScenario) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *OIDCScenario) TestShouldAuthorizeAccessToOIDCApp() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doVisit(s.T(), OIDCBaseURL)
s.verifyIsFirstFactorPage(ctx, s.T())
s.doFillLoginPageAndClick(ctx, s.T(), "john", "password", false)
s.verifyIsSecondFactorPage(ctx, s.T())
s.doValidateTOTP(ctx, s.T(), s.secret)
time.Sleep(2 * time.Second)
s.doVisit(s.T(), s.Context(ctx), OIDCBaseURL)
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
s.doFillLoginPageAndClick(s.T(), s.Context(ctx), "john", "password", false)
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
s.doValidateTOTP(s.T(), s.Context(ctx), s.secret)
s.waitBodyContains(ctx, s.T(), "Not logged yet...")
s.waitBodyContains(s.T(), s.Context(ctx), "Not logged yet...")
// this href represents the 'login' link
err := s.WaitElementLocatedByTagName(ctx, s.T(), "a").Click()
// Search for the 'login' link
err := s.Page.MustSearch("Log in").Click("left")
assert.NoError(s.T(), err)
s.verifyIsConsentPage(ctx, s.T())
err = s.WaitElementLocatedByID(ctx, s.T(), "accept-button").Click()
s.verifyIsConsentPage(s.T(), s.Context(ctx))
err = s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "accept-button").Click("left")
assert.NoError(s.T(), err)
// Verify that the app is showing the info related to the user stored in the JWT token
time.Sleep(2 * time.Second)
s.waitBodyContains(ctx, s.T(), "Logged in as john!")
s.waitBodyContains(s.T(), s.Context(ctx), "Logged in as john!")
}
func (s *OIDCScenario) TestShouldDenyConsent() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doVisit(s.T(), OIDCBaseURL)
s.verifyIsFirstFactorPage(ctx, s.T())
s.doFillLoginPageAndClick(ctx, s.T(), "john", "password", false)
s.verifyIsSecondFactorPage(ctx, s.T())
s.doValidateTOTP(ctx, s.T(), s.secret)
time.Sleep(1 * time.Second)
s.doVisit(s.T(), s.Context(ctx), OIDCBaseURL)
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
s.doFillLoginPageAndClick(s.T(), s.Context(ctx), "john", "password", false)
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
s.doValidateTOTP(s.T(), s.Context(ctx), s.secret)
s.waitBodyContains(ctx, s.T(), "Not logged yet...")
s.waitBodyContains(s.T(), s.Context(ctx), "Not logged yet...")
// this href represents the 'login' link
err := s.WaitElementLocatedByTagName(ctx, s.T(), "a").Click()
// Search for the 'login' link
err := s.Page.MustSearch("Log in").Click("left")
assert.NoError(s.T(), err)
s.verifyIsConsentPage(ctx, s.T())
s.verifyIsConsentPage(s.T(), s.Context(ctx))
err = s.WaitElementLocatedByID(ctx, s.T(), "deny-button").Click()
err = s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "deny-button").Click("left")
assert.NoError(s.T(), err)
time.Sleep(1 * time.Second)
s.verifyURLIs(ctx, s.T(), "https://oidc.example.com:8080/oauth2/callback?error=access_denied&error_description=User%20has%20rejected%20the%20scopes")
s.verifyIsOIDC(s.T(), s.Context(ctx), "oauth2:", "https://oidc.example.com:8080/oauth2/callback?error=access_denied&error_description=User%20has%20rejected%20the%20scopes")
}
func TestRunOIDCScenario(t *testing.T) {

View File

@ -11,27 +11,27 @@ import (
)
type OneFactorSuite struct {
*SeleniumSuite
*RodSuite
}
func NewOneFactorScenario() *OneFactorSuite {
return &OneFactorSuite{
SeleniumSuite: new(SeleniumSuite),
RodSuite: new(RodSuite),
}
}
func (s *OneFactorSuite) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *OneFactorSuite) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -39,40 +39,50 @@ func (s *OneFactorSuite) TearDownSuite() {
}
func (s *OneFactorSuite) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
func (s *OneFactorSuite) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *OneFactorSuite) TestShouldAuthorizeSecretAfterOneFactor() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
targetURL := fmt.Sprintf("%s/secret.html", SingleFactorBaseURL)
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, targetURL)
s.verifySecretAuthorized(ctx, s.T())
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, targetURL)
s.verifySecretAuthorized(s.T(), s.Page)
}
func (s *OneFactorSuite) TestShouldRedirectToSecondFactor() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, targetURL)
s.verifyIsSecondFactorPage(ctx, s.T())
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, targetURL)
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
}
func (s *OneFactorSuite) TestShouldDenyAccessOnBadPassword() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
s.doLoginOneFactor(ctx, s.T(), "john", "bad-password", false, targetURL)
s.verifyIsFirstFactorPage(ctx, s.T())
s.verifyNotificationDisplayed(ctx, s.T(), "Incorrect username or password.")
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "bad-password", false, targetURL)
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Incorrect username or password.")
}
func TestRunOneFactor(t *testing.T) {

View File

@ -10,25 +10,25 @@ import (
)
type PasswordComplexityScenario struct {
*SeleniumSuite
*RodSuite
}
func NewPasswordComplexityScenario() *PasswordComplexityScenario {
return &PasswordComplexityScenario{SeleniumSuite: new(SeleniumSuite)}
return &PasswordComplexityScenario{RodSuite: new(RodSuite)}
}
func (s *PasswordComplexityScenario) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *PasswordComplexityScenario) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -36,24 +36,28 @@ func (s *PasswordComplexityScenario) TearDownSuite() {
}
func (s *PasswordComplexityScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
func (s *PasswordComplexityScenario) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *PasswordComplexityScenario) TestShouldRejectPasswordReset() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsFirstFactorPage(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL())
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
// Attempt to reset the password to a
s.doResetPassword(ctx, s.T(), "john", "a", "a", true)
s.verifyNotificationDisplayed(ctx, s.T(), "Your supplied password does not meet the password policy requirements.")
s.doResetPassword(s.T(), s.Context(ctx), "john", "a", "a", true)
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Your supplied password does not meet the password policy requirements.")
}
func TestRunPasswordComplexityScenario(t *testing.T) {

View File

@ -10,27 +10,27 @@ import (
)
type RedirectionCheckScenario struct {
*SeleniumSuite
*RodSuite
}
func NewRedirectionCheckScenario() *RedirectionCheckScenario {
return &RedirectionCheckScenario{
SeleniumSuite: new(SeleniumSuite),
RodSuite: new(RodSuite),
}
}
func (s *RedirectionCheckScenario) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *RedirectionCheckScenario) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -38,12 +38,13 @@ func (s *RedirectionCheckScenario) TearDownSuite() {
}
func (s *RedirectionCheckScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
func (s *RedirectionCheckScenario) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
var redirectionAuthorizations = map[string]bool{
@ -59,21 +60,24 @@ var redirectionAuthorizations = map[string]bool{
func (s *RedirectionCheckScenario) TestShouldRedirectOnLoginOnlyWhenDomainIsSafe() {
ctx, cancel := context.WithTimeout(context.Background(), 35*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password")
secret := s.doRegisterThenLogout(s.T(), s.Context(ctx), "john", "password")
for url, redirected := range redirectionAuthorizations {
s.T().Run(url, func(t *testing.T) {
s.doLoginTwoFactor(ctx, t, "john", "password", false, secret, url)
s.doLoginTwoFactor(t, s.Context(ctx), "john", "password", false, secret, url)
if redirected {
s.verifySecretAuthorized(ctx, t)
s.verifySecretAuthorized(t, s.Context(ctx))
} else {
s.verifyIsAuthenticatedPage(ctx, t)
s.verifyIsAuthenticatedPage(t, s.Context(ctx))
}
s.doLogout(ctx, t)
s.doLogout(t, s.Context(ctx))
})
}
}
@ -91,11 +95,14 @@ var logoutRedirectionURLs = map[string]bool{
func (s *RedirectionCheckScenario) TestShouldRedirectOnLogoutOnlyWhenDomainIsSafe() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
for url, success := range logoutRedirectionURLs {
s.T().Run(url, func(t *testing.T) {
s.doLogoutWithRedirect(ctx, t, url, !success)
s.doLogoutWithRedirect(t, s.Context(ctx), url, !success)
})
}
}

View File

@ -11,50 +11,54 @@ import (
)
type RedirectionURLScenario struct {
*SeleniumSuite
*RodSuite
}
func NewRedirectionURLScenario() *RedirectionURLScenario {
return &RedirectionURLScenario{
SeleniumSuite: new(SeleniumSuite),
RodSuite: new(RodSuite),
}
}
func (rus *RedirectionURLScenario) SetupSuite() {
wds, err := StartWebDriver()
func (s *RedirectionURLScenario) SetupSuite() {
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
rus.WebDriverSession = wds
s.RodSession = browser
}
func (rus *RedirectionURLScenario) TearDownSuite() {
err := rus.WebDriverSession.Stop()
func (s *RedirectionURLScenario) TearDownSuite() {
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
}
}
func (rus *RedirectionURLScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
rus.doLogout(ctx, rus.T())
rus.doVisit(rus.T(), HomeBaseURL)
rus.verifyIsHome(ctx, rus.T())
func (s *RedirectionURLScenario) SetupTest() {
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
func (rus *RedirectionURLScenario) TestShouldVerifyCustomURLParametersArePropagatedAfterRedirection() {
func (s *RedirectionURLScenario) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *RedirectionURLScenario) TestShouldVerifyCustomURLParametersArePropagatedAfterRedirection() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
targetURL := fmt.Sprintf("%s/secret.html?myparam=test", SingleFactorBaseURL)
rus.doLoginOneFactor(ctx, rus.T(), "john", "password", false, targetURL)
rus.verifySecretAuthorized(ctx, rus.T())
rus.verifyURLIs(ctx, rus.T(), targetURL)
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, targetURL)
s.verifySecretAuthorized(s.T(), s.Context(ctx))
s.verifyURLIs(s.T(), s.Context(ctx), targetURL)
}
func TestRedirectionURLScenario(t *testing.T) {

View File

@ -11,27 +11,27 @@ import (
)
type RegulationScenario struct {
*SeleniumSuite
*RodSuite
}
func NewRegulationScenario() *RegulationScenario {
return &RegulationScenario{
SeleniumSuite: new(SeleniumSuite),
RodSuite: new(RodSuite),
}
}
func (s *RegulationScenario) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *RegulationScenario) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -39,47 +39,49 @@ func (s *RegulationScenario) TearDownSuite() {
}
func (s *RegulationScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
func (s *RegulationScenario) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *RegulationScenario) TestShouldBanUserAfterTooManyAttempt() {
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doVisitLoginPage(ctx, s.T(), "")
s.doFillLoginPageAndClick(ctx, s.T(), "john", "bad-password", false)
s.verifyNotificationDisplayed(ctx, s.T(), "Incorrect username or password.")
s.doVisitLoginPage(s.T(), s.Context(ctx), "")
s.doFillLoginPageAndClick(s.T(), s.Context(ctx), "john", "bad-password", false)
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Incorrect username or password.")
for i := 0; i < 3; i++ {
err := s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").SendKeys("bad-password")
err := s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "password-textfield").Input("bad-password")
require.NoError(s.T(), err)
err = s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click()
err = s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "sign-in-button").Click("left")
require.NoError(s.T(), err)
time.Sleep(1 * time.Second)
}
// Enter the correct password and test the regulation lock out
err := s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").SendKeys("password")
err := s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "password-textfield").Input("password")
require.NoError(s.T(), err)
err = s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click()
err = s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "sign-in-button").Click("left")
require.NoError(s.T(), err)
s.verifyNotificationDisplayed(ctx, s.T(), "Incorrect username or password.")
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Incorrect username or password.")
time.Sleep(1 * time.Second)
s.verifyIsFirstFactorPage(ctx, s.T())
time.Sleep(9 * time.Second)
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
time.Sleep(10 * time.Second)
// Enter the correct password and test a successful login
err = s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").SendKeys("password")
err = s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "password-textfield").Input("password")
require.NoError(s.T(), err)
err = s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click()
err = s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "sign-in-button").Click("left")
require.NoError(s.T(), err)
s.verifyIsSecondFactorPage(ctx, s.T())
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
}
func TestBlacklistingScenario(t *testing.T) {

View File

@ -10,25 +10,25 @@ import (
)
type ResetPasswordScenario struct {
*SeleniumSuite
*RodSuite
}
func NewResetPasswordScenario() *ResetPasswordScenario {
return &ResetPasswordScenario{SeleniumSuite: new(SeleniumSuite)}
return &ResetPasswordScenario{RodSuite: new(RodSuite)}
}
func (s *ResetPasswordScenario) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *ResetPasswordScenario) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -36,64 +36,74 @@ func (s *ResetPasswordScenario) TearDownSuite() {
}
func (s *ResetPasswordScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
func (s *ResetPasswordScenario) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *ResetPasswordScenario) TestShouldResetPassword() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsFirstFactorPage(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL())
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
// Reset the password to abc
s.doResetPassword(ctx, s.T(), "john", "abc", "abc", false)
s.doResetPassword(s.T(), s.Context(ctx), "john", "abc", "abc", false)
// Try to login with the old password
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
s.verifyNotificationDisplayed(ctx, s.T(), "Incorrect username or password.")
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Incorrect username or password.")
// Try to login with the new password
s.doLoginOneFactor(ctx, s.T(), "john", "abc", false, "")
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "abc", false, "")
// Logout
s.doLogout(ctx, s.T())
s.doLogout(s.T(), s.Context(ctx))
// Reset the original password
s.doResetPassword(ctx, s.T(), "john", "password", "password", false)
s.doResetPassword(s.T(), s.Context(ctx), "john", "password", "password", false)
}
func (s *ResetPasswordScenario) TestShouldMakeAttackerThinkPasswordResetIsInitiated() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsFirstFactorPage(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL())
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
// Try to initiate a password reset of an nonexistent user.
s.doInitiatePasswordReset(ctx, s.T(), "i_dont_exist")
s.doInitiatePasswordReset(s.T(), s.Context(ctx), "i_dont_exist")
// Check that the notification make the attacker thinks the process is initiated
s.verifyMailNotificationDisplayed(ctx, s.T())
s.verifyMailNotificationDisplayed(s.T(), s.Context(ctx))
}
func (s *ResetPasswordScenario) TestShouldLetUserNoticeThereIsAPasswordMismatch() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsFirstFactorPage(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL())
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
s.doInitiatePasswordReset(ctx, s.T(), "john")
s.verifyMailNotificationDisplayed(ctx, s.T())
s.doInitiatePasswordReset(s.T(), s.Context(ctx), "john")
s.verifyMailNotificationDisplayed(s.T(), s.Context(ctx))
s.doCompletePasswordReset(ctx, s.T(), "password", "another_password")
s.verifyNotificationDisplayed(ctx, s.T(), "Passwords do not match.")
s.doCompletePasswordReset(s.T(), s.Context(ctx), "password", "another_password")
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Passwords do not match.")
}
func TestRunResetPasswordScenario(t *testing.T) {

View File

@ -13,27 +13,27 @@ import (
)
type SigninEmailScenario struct {
*SeleniumSuite
*RodSuite
}
func NewSigninEmailScenario() *SigninEmailScenario {
return &SigninEmailScenario{
SeleniumSuite: new(SeleniumSuite),
RodSuite: new(RodSuite),
}
}
func (s *SigninEmailScenario) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *SigninEmailScenario) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -41,21 +41,25 @@ func (s *SigninEmailScenario) TearDownSuite() {
}
func (s *SigninEmailScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
func (s *SigninEmailScenario) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *SigninEmailScenario) TestShouldSignInWithUserEmail() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
targetURL := fmt.Sprintf("%s/secret.html", SingleFactorBaseURL)
s.doLoginOneFactor(ctx, s.T(), "john.doe@authelia.com", "password", false, targetURL)
s.verifySecretAuthorized(ctx, s.T())
s.doLoginOneFactor(s.T(), s.Context(ctx), "john.doe@authelia.com", "password", false, targetURL)
s.verifySecretAuthorized(s.T(), s.Context(ctx))
}
func TestSigninEmailScenario(t *testing.T) {

View File

@ -11,27 +11,27 @@ import (
)
type TwoFactorSuite struct {
*SeleniumSuite
*RodSuite
}
func NewTwoFactorScenario() *TwoFactorSuite {
return &TwoFactorSuite{
SeleniumSuite: new(SeleniumSuite),
RodSuite: new(RodSuite),
}
}
func (s *TwoFactorSuite) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *TwoFactorSuite) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -39,62 +39,57 @@ func (s *TwoFactorSuite) TearDownSuite() {
}
func (s *TwoFactorSuite) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
func (s *TwoFactorSuite) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *TwoFactorSuite) TestShouldAuthorizeSecretAfterTwoFactor() {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
username := testUsername
password := testPassword
// Login one factor
s.doLoginOneFactor(ctx, s.T(), username, password, false, "")
// Check he reaches the 2FA stage
s.verifyIsSecondFactorPage(ctx, s.T())
// Then register the TOTP factor
secret := s.doRegisterTOTP(ctx, s.T())
// And logout
s.doLogout(ctx, s.T())
// Login again with 1FA & 2FA
// Login and register TOTP, logout and login again with 1FA & 2FA
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
s.doLoginTwoFactor(ctx, s.T(), testUsername, testPassword, false, secret, targetURL)
_ = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), username, password, false, targetURL)
// And check if the user is redirected to the secret.
s.verifySecretAuthorized(ctx, s.T())
s.verifySecretAuthorized(s.T(), s.Context(ctx))
// Leave the secret
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
s.verifyIsHome(s.T(), s.Context(ctx))
// And try to reload it again to check the session is kept
s.doVisit(s.T(), targetURL)
s.verifySecretAuthorized(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), targetURL)
s.verifySecretAuthorized(s.T(), s.Context(ctx))
}
func (s *TwoFactorSuite) TestShouldFailTwoFactor() {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
// Register TOTP secret and logout.
s.doRegisterThenLogout(ctx, s.T(), testUsername, testPassword)
s.doRegisterThenLogout(s.T(), s.Context(ctx), testUsername, testPassword)
wrongPasscode := "123456"
s.doLoginOneFactor(ctx, s.T(), testUsername, testPassword, false, "")
s.verifyIsSecondFactorPage(ctx, s.T())
s.doEnterOTP(ctx, s.T(), wrongPasscode)
s.verifyNotificationDisplayed(ctx, s.T(), "The one-time password might be wrong")
s.doLoginOneFactor(s.T(), s.Context(ctx), testUsername, testPassword, false, "")
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
s.doEnterOTP(s.T(), s.Context(ctx), wrongPasscode)
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "The one-time password might be wrong")
}
func TestRunTwoFactor(t *testing.T) {

View File

@ -10,27 +10,27 @@ import (
)
type UserPreferencesScenario struct {
*SeleniumSuite
*RodSuite
}
func NewUserPreferencesScenario() *UserPreferencesScenario {
return &UserPreferencesScenario{
SeleniumSuite: new(SeleniumSuite),
RodSuite: new(RodSuite),
}
}
func (s *UserPreferencesScenario) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *UserPreferencesScenario) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -38,59 +38,63 @@ func (s *UserPreferencesScenario) TearDownSuite() {
}
func (s *UserPreferencesScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
func (s *UserPreferencesScenario) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *UserPreferencesScenario) TestShouldRememberLastUsed2FAMethod() {
ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
// Authenticate
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
s.verifyIsSecondFactorPage(ctx, s.T())
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
// Then switch to push notification method
s.doChangeMethod(ctx, s.T(), "push-notification")
s.WaitElementLocatedByID(ctx, s.T(), "push-notification-method")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "push-notification-method")
// Switch context to clean up state in portal.
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
s.verifyIsHome(s.T(), s.Context(ctx))
// Then go back to portal.
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsSecondFactorPage(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL())
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
// And check the latest method is still used.
s.WaitElementLocatedByID(ctx, s.T(), "push-notification-method")
s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "push-notification-method")
// Meaning the authentication is successful
s.verifyIsHome(ctx, s.T())
s.verifyIsHome(s.T(), s.Context(ctx))
// 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(s.T(), s.Context(ctx))
s.doLoginOneFactor(s.T(), s.Context(ctx), "harry", "password", false, "")
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "one-time-password-method")
s.doLogout(ctx, s.T())
s.verifyIsFirstFactorPage(ctx, s.T())
s.doLogout(s.T(), s.Context(ctx))
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
// 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.verifyIsHome(ctx, s.T())
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "push-notification-method")
s.verifyIsHome(s.T(), s.Context(ctx))
s.doLogout(ctx, s.T())
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
s.doLogout(s.T(), s.Context(ctx))
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
// Eventually restore the default method
s.doChangeMethod(ctx, s.T(), "one-time-password")
s.WaitElementLocatedByID(ctx, s.T(), "one-time-password-method")
s.doChangeMethod(s.T(), s.Context(ctx), "one-time-password")
s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "one-time-password-method")
}
func TestUserPreferencesScenario(t *testing.T) {

View File

@ -7,11 +7,11 @@ import (
)
type ActiveDirectorySuite struct {
*SeleniumSuite
*RodSuite
}
func NewActiveDirectorySuite() *ActiveDirectorySuite {
return &ActiveDirectorySuite{SeleniumSuite: new(SeleniumSuite)}
return &ActiveDirectorySuite{RodSuite: new(RodSuite)}
}
func (s *ActiveDirectorySuite) TestOneFactorScenario() {

View File

@ -11,40 +11,53 @@ import (
)
type BypassAllWebDriverSuite struct {
*SeleniumSuite
*RodSuite
}
func NewBypassAllWebDriverSuite() *BypassAllWebDriverSuite {
return &BypassAllWebDriverSuite{SeleniumSuite: new(SeleniumSuite)}
return &BypassAllWebDriverSuite{RodSuite: new(RodSuite)}
}
func (s *BypassAllWebDriverSuite) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *BypassAllWebDriverSuite) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
}
}
func (s *BypassAllWebDriverSuite) SetupTest() {
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
func (s *BypassAllWebDriverSuite) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *BypassAllWebDriverSuite) TestShouldAccessPublicResource() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doVisit(s.T(), fmt.Sprintf("%s/secret.html", AdminBaseURL))
s.verifySecretAuthorized(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/secret.html", AdminBaseURL))
s.verifySecretAuthorized(s.T(), s.Context(ctx))
s.doVisit(s.T(), fmt.Sprintf("%s/secret.html", PublicBaseURL))
s.verifySecretAuthorized(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/secret.html", PublicBaseURL))
s.verifySecretAuthorized(s.T(), s.Context(ctx))
}
type BypassAllSuite struct {

View File

@ -7,11 +7,11 @@ import (
)
type DockerSuite struct {
*SeleniumSuite
*RodSuite
}
func NewDockerSuite() *DockerSuite {
return &DockerSuite{SeleniumSuite: new(SeleniumSuite)}
return &DockerSuite{RodSuite: new(RodSuite)}
}
func (s *DockerSuite) TestOneFactorScenario() {

View File

@ -10,25 +10,25 @@ import (
)
type DuoPushWebDriverSuite struct {
*SeleniumSuite
*RodSuite
}
func NewDuoPushWebDriverSuite() *DuoPushWebDriverSuite {
return &DuoPushWebDriverSuite{SeleniumSuite: new(SeleniumSuite)}
return &DuoPushWebDriverSuite{RodSuite: new(RodSuite)}
}
func (s *DuoPushWebDriverSuite) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *DuoPushWebDriverSuite) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -36,65 +36,75 @@ func (s *DuoPushWebDriverSuite) TearDownSuite() {
}
func (s *DuoPushWebDriverSuite) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.doLogout(ctx, s.T())
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
func (s *DuoPushWebDriverSuite) TearDownTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
s.doLogout(ctx, s.T())
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
s.verifyIsSecondFactorPage(ctx, s.T())
s.doChangeMethod(ctx, s.T(), "one-time-password")
s.WaitElementLocatedByID(ctx, s.T(), "one-time-password-method")
s.collectCoverage(s.Page)
s.MustClose()
}()
s.doLogout(s.T(), s.Context(ctx))
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
s.doChangeMethod(s.T(), s.Context(ctx), "one-time-password")
s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "one-time-password-method")
}
func (s *DuoPushWebDriverSuite) TestShouldSucceedAuthentication() {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
ConfigureDuo(s.T(), Allow)
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
s.doChangeMethod(ctx, s.T(), "push-notification")
s.verifyIsHome(ctx, s.T())
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.verifyIsHome(s.T(), s.Context(ctx))
}
func (s *DuoPushWebDriverSuite) TestShouldFailAuthentication() {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
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")
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.WaitElementLocatedByClassName(s.T(), s.Context(ctx), "failure-icon")
}
type DuoPushDefaultRedirectionSuite struct {
*SeleniumSuite
*RodSuite
}
func NewDuoPushDefaultRedirectionSuite() *DuoPushDefaultRedirectionSuite {
return &DuoPushDefaultRedirectionSuite{SeleniumSuite: new(SeleniumSuite)}
return &DuoPushDefaultRedirectionSuite{RodSuite: new(RodSuite)}
}
func (s *DuoPushDefaultRedirectionSuite) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *DuoPushDefaultRedirectionSuite) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -102,19 +112,25 @@ func (s *DuoPushDefaultRedirectionSuite) TearDownSuite() {
}
func (s *DuoPushDefaultRedirectionSuite) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
func (s *DuoPushDefaultRedirectionSuite) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *DuoPushDefaultRedirectionSuite) TestUserIsRedirectedToDefaultURL() {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
s.doChangeMethod(ctx, s.T(), "push-notification")
s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/")
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
s.verifyIsHome(s.T(), s.Page)
}
type DuoPushSuite struct {

View File

@ -42,6 +42,13 @@ func init() {
fmt.Println(frontendLogs)
haproxyLogs, err := dockerEnvironment.Logs("haproxy", nil)
if err != nil {
return err
}
fmt.Println(haproxyLogs)
return nil
}

View File

@ -7,11 +7,11 @@ import (
)
type HAProxySuite struct {
*SeleniumSuite
*RodSuite
}
func NewHAProxySuite() *HAProxySuite {
return &HAProxySuite{SeleniumSuite: new(SeleniumSuite)}
return &HAProxySuite{RodSuite: new(RodSuite)}
}
func (s *HAProxySuite) TestOneFactorScenario() {

View File

@ -13,25 +13,25 @@ import (
)
type HighAvailabilityWebDriverSuite struct {
*SeleniumSuite
*RodSuite
}
func NewHighAvailabilityWebDriverSuite() *HighAvailabilityWebDriverSuite {
return &HighAvailabilityWebDriverSuite{SeleniumSuite: new(SeleniumSuite)}
return &HighAvailabilityWebDriverSuite{RodSuite: new(RodSuite)}
}
func (s *HighAvailabilityWebDriverSuite) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *HighAvailabilityWebDriverSuite) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -39,37 +39,42 @@ func (s *HighAvailabilityWebDriverSuite) TearDownSuite() {
}
func (s *HighAvailabilityWebDriverSuite) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
func (s *HighAvailabilityWebDriverSuite) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserSessionActive() {
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password")
secret := s.doRegisterThenLogout(s.T(), s.Context(ctx), "john", "password")
err := haDockerEnvironment.Restart("redis-node-0")
s.Require().NoError(err)
time.Sleep(5 * time.Second)
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "")
s.verifyIsSecondFactorPage(ctx, s.T())
s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, "")
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
}
func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserSessionActiveWithPrimaryRedisNodeFailure() {
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password")
secret := s.doRegisterThenLogout(s.T(), s.Context(ctx), "john", "password")
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "")
s.verifyIsSecondFactorPage(ctx, s.T())
s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, "")
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
err := haDockerEnvironment.Stop("redis-node-0")
s.Require().NoError(err)
@ -79,32 +84,32 @@ func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserSessionActiveWithPrim
s.Require().NoError(err)
}()
// Allow fail over to occur.
time.Sleep(3 * time.Second)
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
s.verifyIsHome(s.T(), s.Context(ctx))
// Verify the user is still authenticated
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsSecondFactorPage(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL())
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
// Then logout and login again to check we can see the secret.
s.doLogout(ctx, s.T())
s.verifyIsFirstFactorPage(ctx, s.T())
s.doLogout(s.T(), s.Context(ctx))
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, fmt.Sprintf("%s/secret.html", SecureBaseURL))
s.verifySecretAuthorized(ctx, s.T())
s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, fmt.Sprintf("%s/secret.html", SecureBaseURL))
s.verifySecretAuthorized(s.T(), s.Context(ctx))
}
func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserSessionActiveWithPrimaryRedisSentinelFailureAndSecondaryRedisNodeFailure() {
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password")
secret := s.doRegisterThenLogout(s.T(), s.Context(ctx), "john", "password")
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "")
s.verifyIsSecondFactorPage(ctx, s.T())
s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, "")
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
err := haDockerEnvironment.Stop("redis-sentinel-0")
s.Require().NoError(err)
@ -122,38 +127,39 @@ func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserSessionActiveWithPrim
s.Require().NoError(err)
}()
// Allow fail over to occur.
time.Sleep(3 * time.Second)
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
s.verifyIsHome(s.T(), s.Context(ctx))
// Verify the user is still authenticated
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsSecondFactorPage(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL())
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
}
func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserDataInDB() {
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password")
secret := s.doRegisterThenLogout(s.T(), s.Context(ctx), "john", "password")
err := haDockerEnvironment.Restart("mariadb")
s.Require().NoError(err)
time.Sleep(20 * time.Second)
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "")
s.verifyIsSecondFactorPage(ctx, s.T())
s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, "")
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
}
func (s *HighAvailabilityWebDriverSuite) TestShouldKeepSessionAfterAutheliaRestart() {
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
secret := s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, "")
s.verifyIsSecondFactorPage(ctx, s.T())
secret := s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, "")
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
err := haDockerEnvironment.Restart("authelia-backend")
s.Require().NoError(err)
@ -161,19 +167,19 @@ func (s *HighAvailabilityWebDriverSuite) TestShouldKeepSessionAfterAutheliaResta
err = waitUntilAutheliaBackendIsReady(haDockerEnvironment)
s.Require().NoError(err)
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
s.verifyIsHome(s.T(), s.Context(ctx))
// Verify the user is still authenticated
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsSecondFactorPage(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL())
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
// Then logout and login again to check the secret is still there
s.doLogout(ctx, s.T())
s.verifyIsFirstFactorPage(ctx, s.T())
s.doLogout(s.T(), s.Context(ctx))
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, fmt.Sprintf("%s/secret.html", SecureBaseURL))
s.verifySecretAuthorized(ctx, s.T())
s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, fmt.Sprintf("%s/secret.html", SecureBaseURL))
s.verifySecretAuthorized(s.T(), s.Context(ctx))
}
var UserJohn = "john"
@ -220,31 +226,34 @@ var expectedAuthorizations = map[string](map[string]bool){
}
func (s *HighAvailabilityWebDriverSuite) TestShouldVerifyAccessControl() {
verifyUserIsAuthorized := func(ctx context.Context, t *testing.T, username, targetURL string, authorized bool) { //nolint:unparam
s.doVisit(t, targetURL)
s.verifyURLIs(ctx, t, targetURL)
verifyUserIsAuthorized := func(ctx context.Context, t *testing.T, targetURL string, authorized bool) {
s.doVisit(t, s.Context(ctx), targetURL)
s.verifyURLIs(t, s.Context(ctx), targetURL)
if authorized {
s.verifySecretAuthorized(ctx, t)
s.verifySecretAuthorized(t, s.Context(ctx))
} else {
s.verifyBodyContains(ctx, t, "403 Forbidden")
s.verifyBodyContains(t, s.Context(ctx), "403 Forbidden")
}
}
verifyAuthorization := func(username string) func(t *testing.T) {
return func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
defer func() {
s.collectScreenshot(ctx.Err(), s.Page)
cancel()
}()
s.doRegisterAndLogin2FA(ctx, t, username, "password", false, "")
s.doRegisterAndLogin2FA(t, s.Context(ctx), username, "password", false, "")
for url, authorizations := range expectedAuthorizations {
t.Run(url, func(t *testing.T) {
verifyUserIsAuthorized(ctx, t, username, url, authorizations[username])
verifyUserIsAuthorized(ctx, t, url, authorizations[username])
})
}
s.doLogout(ctx, t)
s.doLogout(t, s.Context(ctx))
}
}

View File

@ -7,11 +7,11 @@ import (
)
type KubernetesSuite struct {
*SeleniumSuite
*RodSuite
}
func NewKubernetesSuite() *KubernetesSuite {
return &KubernetesSuite{SeleniumSuite: new(SeleniumSuite)}
return &KubernetesSuite{RodSuite: new(RodSuite)}
}
func (s *KubernetesSuite) TestOneFactorScenario() {

View File

@ -7,11 +7,11 @@ import (
)
type LDAPSuite struct {
*SeleniumSuite
*RodSuite
}
func NewLDAPSuite() *LDAPSuite {
return &LDAPSuite{SeleniumSuite: new(SeleniumSuite)}
return &LDAPSuite{RodSuite: new(RodSuite)}
}
func (s *LDAPSuite) TestOneFactorScenario() {

View File

@ -7,11 +7,11 @@ import (
)
type MariadbSuite struct {
*SeleniumSuite
*RodSuite
}
func NewMariadbSuite() *MariadbSuite {
return &MariadbSuite{SeleniumSuite: new(SeleniumSuite)}
return &MariadbSuite{RodSuite: new(RodSuite)}
}
func (s *MariadbSuite) TestOneFactorScenario() {

View File

@ -7,11 +7,11 @@ import (
)
type MySQLSuite struct {
*SeleniumSuite
*RodSuite
}
func NewMySQLSuite() *MySQLSuite {
return &MySQLSuite{SeleniumSuite: new(SeleniumSuite)}
return &MySQLSuite{RodSuite: new(RodSuite)}
}
func (s *MySQLSuite) TestOneFactorScenario() {

View File

@ -21,20 +21,21 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon2FA() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
wds, err := StartWebDriver()
browser, err := StartRod()
s.Require().NoError(err)
defer func() {
err = wds.Stop()
err = browser.WebDriver.Close()
s.Require().NoError(err)
browser.Launcher.Cleanup()
}()
targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL)
wds.doVisit(s.T(), targetURL)
wds.verifyIsFirstFactorPage(ctx, s.T())
page := browser.doCreateTab(s.T(), targetURL).Context(ctx)
wds.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, targetURL)
wds.verifySecretAuthorized(ctx, s.T())
browser.verifyIsFirstFactorPage(s.T(), page)
browser.doRegisterAndLogin2FA(s.T(), page, "john", "password", false, targetURL)
browser.verifySecretAuthorized(s.T(), page)
}
// from network 192.168.240.201/32.
@ -42,21 +43,22 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon1FA() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
wds, err := StartWebDriverWithProxy("http://proxy-client1.example.com:3128", GetWebDriverPort())
browser, err := StartRodWithProxy("http://proxy-client1.example.com:3128")
s.Require().NoError(err)
defer func() {
err = wds.Stop()
err = browser.WebDriver.Close()
s.Require().NoError(err)
browser.Launcher.Cleanup()
}()
targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL)
wds.doVisit(s.T(), targetURL)
wds.verifyIsFirstFactorPage(ctx, s.T())
page := browser.doCreateTab(s.T(), targetURL).Context(ctx)
wds.doLoginOneFactor(ctx, s.T(), "john", "password",
browser.verifyIsFirstFactorPage(s.T(), page)
browser.doLoginOneFactor(s.T(), page, "john", "password",
false, fmt.Sprintf("%s/secret.html", SecureBaseURL))
wds.verifySecretAuthorized(ctx, s.T())
browser.verifySecretAuthorized(s.T(), page)
}
// from network 192.168.240.202/32.
@ -64,16 +66,18 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon0FA() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
wds, err := StartWebDriverWithProxy("http://proxy-client2.example.com:3128", GetWebDriverPort())
browser, err := StartRodWithProxy("http://proxy-client2.example.com:3128")
s.Require().NoError(err)
defer func() {
err = wds.Stop()
err = browser.WebDriver.Close()
s.Require().NoError(err)
browser.Launcher.Cleanup()
}()
wds.doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL))
wds.verifySecretAuthorized(ctx, s.T())
page := browser.doCreateTab(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL)).Context(ctx)
browser.verifySecretAuthorized(s.T(), page)
}
func TestNetworkACLSuite(t *testing.T) {

View File

@ -7,11 +7,11 @@ import (
)
type OIDCSuite struct {
*SeleniumSuite
*RodSuite
}
func NewOIDCSuite() *OIDCSuite {
return &OIDCSuite{SeleniumSuite: new(SeleniumSuite)}
return &OIDCSuite{RodSuite: new(RodSuite)}
}
func (s *OIDCSuite) TestOIDCScenario() {

View File

@ -7,11 +7,11 @@ import (
)
type OIDCTraefikSuite struct {
*SeleniumSuite
*RodSuite
}
func NewOIDCTraefikSuite() *OIDCTraefikSuite {
return &OIDCTraefikSuite{SeleniumSuite: new(SeleniumSuite)}
return &OIDCTraefikSuite{RodSuite: new(RodSuite)}
}
func (s *OIDCTraefikSuite) TestOIDCScenario() {

View File

@ -15,25 +15,25 @@ type OneFactorOnlySuite struct {
}
type OneFactorOnlyWebSuite struct {
*SeleniumSuite
*RodSuite
}
func NewOneFactorOnlyWebSuite() *OneFactorOnlyWebSuite {
return &OneFactorOnlyWebSuite{SeleniumSuite: new(SeleniumSuite)}
return &OneFactorOnlyWebSuite{RodSuite: new(RodSuite)}
}
func (s *OneFactorOnlyWebSuite) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *OneFactorOnlyWebSuite) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -41,62 +41,81 @@ func (s *OneFactorOnlyWebSuite) TearDownSuite() {
}
func (s *OneFactorOnlyWebSuite) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
func (s *OneFactorOnlyWebSuite) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
// No target url is provided, then the user should be redirect to the default url.
func (s *OneFactorOnlyWebSuite) TestShouldRedirectUserToDefaultURL() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/")
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.verifyIsHome(s.T(), s.Context(ctx))
}
// Unsafe URL is provided, then the user should be redirect to the default url.
func (s *OneFactorOnlyWebSuite) TestShouldRedirectUserToDefaultURLWhenURLIsUnsafe() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "http://unsafe.local")
s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/")
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "http://unsafe.local")
s.verifyIsHome(s.T(), s.Context(ctx))
}
// When use logged in and visit the portal again, she gets redirect to the authenticated view.
func (s *OneFactorOnlyWebSuite) TestShouldDisplayAuthenticatedView() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/")
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsAuthenticatedPage(ctx, s.T())
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.verifyIsHome(s.T(), s.Context(ctx))
s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL())
s.verifyIsAuthenticatedPage(s.T(), s.Context(ctx))
}
func (s *OneFactorOnlyWebSuite) TestShouldRedirectAlreadyAuthenticatedUser() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/")
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.verifyIsHome(s.T(), s.Context(ctx))
s.doVisit(s.T(), fmt.Sprintf("%s?rd=https://singlefactor.example.com:8080/secret.html", GetLoginBaseURL()))
s.verifyURLIs(ctx, s.T(), "https://singlefactor.example.com:8080/secret.html")
s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s?rd=https://singlefactor.example.com:8080/secret.html", GetLoginBaseURL()))
s.verifySecretAuthorized(s.T(), s.Context(ctx))
s.verifyURLIs(s.T(), s.Context(ctx), "https://singlefactor.example.com:8080/secret.html")
}
func (s *OneFactorOnlyWebSuite) TestShouldNotRedirectAlreadyAuthenticatedUserToUnsafeURL() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/")
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.verifyIsHome(s.T(), s.Context(ctx))
// Visit the login page and wait for redirection to 2FA page with success icon displayed.
s.doVisit(s.T(), fmt.Sprintf("%s?rd=https://secure.example.local:8080", GetLoginBaseURL()))
s.verifyNotificationDisplayed(ctx, s.T(), "Redirection was determined to be unsafe and aborted. Ensure the redirection URL is correct.")
s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s?rd=https://secure.example.local:8080", GetLoginBaseURL()))
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Redirection was determined to be unsafe and aborted. Ensure the redirection URL is correct.")
}
func (s *OneFactorOnlySuite) TestWeb() {

View File

@ -7,11 +7,11 @@ import (
)
type PathPrefixSuite struct {
*SeleniumSuite
*RodSuite
}
func NewPathPrefixSuite() *PathPrefixSuite {
return &PathPrefixSuite{SeleniumSuite: new(SeleniumSuite)}
return &PathPrefixSuite{RodSuite: new(RodSuite)}
}
func (s *PathPrefixSuite) TestOneFactorScenario() {

View File

@ -7,11 +7,11 @@ import (
)
type PostgresSuite struct {
*SeleniumSuite
*RodSuite
}
func NewPostgresSuite() *PostgresSuite {
return &PostgresSuite{SeleniumSuite: new(SeleniumSuite)}
return &PostgresSuite{RodSuite: new(RodSuite)}
}
func (s *PostgresSuite) TestOneFactorScenario() {

View File

@ -7,11 +7,11 @@ import (
)
type ShortTimeoutsSuite struct {
*SeleniumSuite
*RodSuite
}
func NewShortTimeoutsSuite() *ShortTimeoutsSuite {
return &ShortTimeoutsSuite{SeleniumSuite: new(SeleniumSuite)}
return &ShortTimeoutsSuite{RodSuite: new(RodSuite)}
}
func (s *ShortTimeoutsSuite) TestDefaultRedirectionURLScenario() {

View File

@ -18,25 +18,25 @@ import (
)
type StandaloneWebDriverSuite struct {
*SeleniumSuite
*RodSuite
}
func NewStandaloneWebDriverSuite() *StandaloneWebDriverSuite {
return &StandaloneWebDriverSuite{SeleniumSuite: new(SeleniumSuite)}
return &StandaloneWebDriverSuite{RodSuite: new(RodSuite)}
}
func (s *StandaloneWebDriverSuite) SetupSuite() {
wds, err := StartWebDriver()
browser, err := StartRod()
if err != nil {
log.Fatal(err)
}
s.WebDriverSession = wds
s.RodSession = browser
}
func (s *StandaloneWebDriverSuite) TearDownSuite() {
err := s.WebDriverSession.Stop()
err := s.RodSession.Stop()
if err != nil {
log.Fatal(err)
@ -44,62 +44,78 @@ func (s *StandaloneWebDriverSuite) TearDownSuite() {
}
func (s *StandaloneWebDriverSuite) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T())
s.WebDriverSession.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
func (s *StandaloneWebDriverSuite) TearDownTest() {
s.collectCoverage(s.Page)
s.MustClose()
}
func (s *StandaloneWebDriverSuite) TestShouldLetUserKnowHeIsAlreadyAuthenticated() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
_ = s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, "")
_ = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, "")
// Visit home page to change context.
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
s.verifyIsHome(s.T(), s.Context(ctx))
// Visit the login page and wait for redirection to 2FA page with success icon displayed.
s.doVisit(s.T(), GetLoginBaseURL())
s.verifyIsAuthenticatedPage(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL())
s.verifyIsAuthenticatedPage(s.T(), s.Context(ctx))
}
func (s *StandaloneWebDriverSuite) TestShouldRedirectAlreadyAuthenticatedUser() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
_ = s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, "")
_ = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, "")
// Visit home page to change context.
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
s.verifyIsHome(s.T(), s.Context(ctx))
// Visit the login page and wait for redirection to 2FA page with success icon displayed.
s.doVisit(s.T(), fmt.Sprintf("%s?rd=https://secure.example.com:8080", GetLoginBaseURL()))
s.verifyURLIs(ctx, s.T(), "https://secure.example.com:8080/")
s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s?rd=https://secure.example.com:8080", GetLoginBaseURL()))
_, err := s.Page.ElementR("h1", "Public resource")
require.NoError(s.T(), err)
s.verifyURLIs(s.T(), s.Context(ctx), "https://secure.example.com:8080/")
}
func (s *StandaloneWebDriverSuite) TestShouldNotRedirectAlreadyAuthenticatedUserToUnsafeURL() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
_ = s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, "")
_ = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, "")
// Visit home page to change context.
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
s.verifyIsHome(s.T(), s.Context(ctx))
// Visit the login page and wait for redirection to 2FA page with success icon displayed.
s.doVisit(s.T(), fmt.Sprintf("%s?rd=https://secure.example.local:8080", GetLoginBaseURL()))
s.verifyNotificationDisplayed(ctx, s.T(), "Redirection was determined to be unsafe and aborted. Ensure the redirection URL is correct.")
s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s?rd=https://secure.example.local:8080", GetLoginBaseURL()))
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Redirection was determined to be unsafe and aborted. Ensure the redirection URL is correct.")
}
func (s *StandaloneWebDriverSuite) TestShouldCheckUserIsAskedToRegisterDevice() {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer func() {
cancel()
s.collectScreenshot(ctx.Err(), s.Page)
}()
username := "john"
password := "password"
@ -109,21 +125,21 @@ func (s *StandaloneWebDriverSuite) TestShouldCheckUserIsAskedToRegisterDevice()
require.NoError(s.T(), provider.DeleteTOTPSecret(username))
// Login one factor.
s.doLoginOneFactor(ctx, s.T(), username, password, false, "")
s.doLoginOneFactor(s.T(), s.Context(ctx), username, password, false, "")
// Check the user is asked to register a new device.
s.WaitElementLocatedByClassName(ctx, s.T(), "state-not-registered")
s.WaitElementLocatedByClassName(s.T(), s.Context(ctx), "state-not-registered")
// Then register the TOTP factor.
s.doRegisterTOTP(ctx, s.T())
s.doRegisterTOTP(s.T(), s.Context(ctx))
// And logout.
s.doLogout(ctx, s.T())
s.doLogout(s.T(), s.Context(ctx))
// Login one factor again.
s.doLoginOneFactor(ctx, s.T(), username, password, false, "")
s.doLoginOneFactor(s.T(), s.Context(ctx), username, password, false, "")
// now the user should be asked to perform 2FA
s.WaitElementLocatedByClassName(ctx, s.T(), "state-method")
s.WaitElementLocatedByClassName(s.T(), s.Context(ctx), "state-method")
}
type StandaloneSuite struct {

View File

@ -10,11 +10,11 @@ import (
)
type Traefik2Suite struct {
*SeleniumSuite
*RodSuite
}
func NewTraefik2Suite() *Traefik2Suite {
return &Traefik2Suite{SeleniumSuite: new(SeleniumSuite)}
return &Traefik2Suite{RodSuite: new(RodSuite)}
}
func (s *Traefik2Suite) TestOneFactorScenario() {
@ -30,31 +30,34 @@ func (s *Traefik2Suite) TestCustomHeaders() {
}
func (s *Traefik2Suite) TestShouldKeepSessionAfterRedisRestart() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
wds, err := StartWebDriver()
s.Require().NoError(err)
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer func() {
err = wds.Stop()
cancel()
s.collectCoverage(s.Page)
s.collectScreenshot(ctx.Err(), s.Page)
s.MustClose()
err := s.RodSession.Stop()
s.Require().NoError(err)
}()
secret := wds.doRegisterThenLogout(ctx, s.T(), "john", "password")
browser, err := StartRod()
s.Require().NoError(err)
s.RodSession = browser
wds.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "")
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
s.verifyIsHome(s.T(), s.Page)
secret := s.doRegisterThenLogout(s.T(), s.Context(ctx), "john", "password")
wds.doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL))
wds.verifySecretAuthorized(ctx, s.T())
s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, "")
s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/secret.html", SecureBaseURL))
s.verifySecretAuthorized(s.T(), s.Context(ctx))
err = traefik2DockerEnvironment.Restart("redis")
s.Require().NoError(err)
time.Sleep(5 * time.Second)
wds.doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL))
wds.verifySecretAuthorized(ctx, s.T())
s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/secret.html", SecureBaseURL))
s.verifySecretAuthorized(s.T(), s.Context(ctx))
}
func TestTraefik2Suite(t *testing.T) {

View File

@ -7,11 +7,11 @@ import (
)
type TraefikSuite struct {
*SeleniumSuite
*RodSuite
}
func NewTraefikSuite() *TraefikSuite {
return &TraefikSuite{SeleniumSuite: new(SeleniumSuite)}
return &TraefikSuite{RodSuite: new(RodSuite)}
}
func (s *TraefikSuite) TestOneFactorScenario() {

View File

@ -1,15 +1,16 @@
package suites
import (
"github.com/go-rod/rod"
"github.com/stretchr/testify/suite"
"github.com/tebeka/selenium"
)
// SeleniumSuite is a selenium suite.
type SeleniumSuite struct {
// RodSuite is a go-rod suite.
type RodSuite struct {
suite.Suite
*WebDriverSession
*RodSession
*rod.Page
}
// CommandSuite is a command line interface suite.
@ -21,8 +22,3 @@ type CommandSuite struct {
*DockerEnvironment
}
// WebDriver return the webdriver of the suite.
func (s *SeleniumSuite) WebDriver() selenium.WebDriver {
return s.WebDriverSession.WebDriver
}

View File

@ -1,11 +1,17 @@
package suites
import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
"runtime"
"strings"
"time"
"github.com/go-rod/rod"
)
// GetLoginBaseURL returns the URL of the login portal and the path prefix if specified.
@ -17,16 +23,51 @@ func GetLoginBaseURL() string {
return LoginBaseURL
}
// GetWebDriverPort returns the port to initialize the webdriver with.
func GetWebDriverPort() int {
driverPort := os.Getenv("CHROMEDRIVER_PORT")
if driverPort == "" {
driverPort = defaultChromeDriverPort
func (rs *RodSession) collectCoverage(page *rod.Page) {
coverageDir := "../../web/.nyc_output"
now := time.Now()
resp, err := page.Eval("JSON.stringify(window.__coverage__)")
if err != nil {
log.Fatal(err)
}
p, _ := strconv.Atoi(driverPort)
coverageData := fmt.Sprintf("%v", resp.Value)
return p
_ = os.MkdirAll(coverageDir, 0775)
if coverageData != "<nil>" {
err = ioutil.WriteFile(fmt.Sprintf("%s/coverage-%d.json", coverageDir, now.Unix()), []byte(coverageData), 0664) //nolint:gosec
if err != nil {
log.Fatal(err)
}
err = filepath.Walk("../../web/.nyc_output", fixCoveragePath)
if err != nil {
log.Fatal(err)
}
}
}
func (rs *RodSession) collectScreenshot(err error, page *rod.Page) {
if err == context.DeadlineExceeded && os.Getenv("CI") == stringTrue {
base := "/buildkite/screenshots"
build := os.Getenv("BUILDKITE_BUILD_NUMBER")
suite := strings.ToLower(os.Getenv("SUITE"))
job := os.Getenv("BUILDKITE_JOB_ID")
path := filepath.Join(fmt.Sprintf("%s/%s/%s/%s", base, build, suite, job)) //nolint: gocritic
if err := os.MkdirAll(path, 0755); err != nil {
log.Fatal(err)
}
pc, _, _, _ := runtime.Caller(2)
fn := runtime.FuncForPC(pc)
p := "github.com/authelia/authelia/v4/internal/suites."
r := strings.NewReplacer(p, "", "(", "", ")", "", "*", "", ".", "-")
page.MustScreenshotFullPage(fmt.Sprintf("%s/%s.jpg", path, r.Replace(fn.Name())))
}
}
func fixCoveragePath(path string, file os.FileInfo, err error) error {

View File

@ -1,33 +1,29 @@
package suites
import (
"context"
"fmt"
"strings"
"testing"
"github.com/go-rod/rod"
"github.com/stretchr/testify/assert"
"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, err := wds.WebDriver.FindElement(selenium.ByTagName, "body")
func (rs *RodSession) verifyBodyContains(t *testing.T, page *rod.Page, pattern string) {
body, err := page.Element("body")
assert.NoError(t, err)
assert.NotNil(t, body)
if err != nil {
return false, err
}
text, err := body.Text()
assert.NoError(t, err)
assert.NotNil(t, text)
if bodyElement == nil {
return false, nil
}
if strings.Contains(text, pattern) {
err = nil
} else {
err = fmt.Errorf("body does not contain pattern: %s", pattern)
}
content, err := bodyElement.Text()
if err != nil {
return false, err
}
return strings.Contains(content, pattern), nil
})
require.NoError(t, err)
}

View File

@ -1,10 +1,11 @@
package suites
import (
"context"
"testing"
"github.com/go-rod/rod"
)
func (wds *WebDriverSession) verifyIsAuthenticatedPage(ctx context.Context, t *testing.T) {
wds.WaitElementLocatedByID(ctx, t, "authenticated-stage")
func (rs *RodSession) verifyIsAuthenticatedPage(t *testing.T, page *rod.Page) {
rs.WaitElementLocatedByCSSSelector(t, page, "authenticated-stage")
}

View File

@ -1,10 +1,11 @@
package suites
import (
"context"
"testing"
"github.com/go-rod/rod"
)
func (wds *WebDriverSession) verifyIsConsentPage(ctx context.Context, t *testing.T) {
wds.WaitElementLocatedByID(ctx, t, "consent-stage")
func (rs *RodSession) verifyIsConsentPage(t *testing.T, page *rod.Page) {
rs.WaitElementLocatedByCSSSelector(t, page, "consent-stage")
}

View File

@ -1,10 +1,11 @@
package suites
import (
"context"
"testing"
"github.com/go-rod/rod"
)
func (wds *WebDriverSession) verifyIsFirstFactorPage(ctx context.Context, t *testing.T) {
wds.WaitElementLocatedByID(ctx, t, "first-factor-stage")
func (rs *RodSession) verifyIsFirstFactorPage(t *testing.T, page *rod.Page) {
rs.WaitElementLocatedByCSSSelector(t, page, "first-factor-stage")
}

View File

@ -1,11 +1,13 @@
package suites
import (
"context"
"fmt"
"testing"
"github.com/go-rod/rod"
)
func (wds *WebDriverSession) verifyIsHome(ctx context.Context, t *testing.T) {
wds.verifyURLIs(ctx, t, fmt.Sprintf("%s/", HomeBaseURL))
func (rs *RodSession) verifyIsHome(t *testing.T, page *rod.Page) {
page.MustElementR("h1", "Access the secret")
rs.verifyURLIs(t, page, fmt.Sprintf("%s/", HomeBaseURL))
}

View File

@ -0,0 +1,12 @@
package suites
import (
"testing"
"github.com/go-rod/rod"
)
func (rs *RodSession) verifyIsOIDC(t *testing.T, page *rod.Page, pattern, url string) {
page.MustElementR("body", pattern)
rs.verifyURLIs(t, page, url)
}

View File

@ -0,0 +1,13 @@
package suites
import (
"fmt"
"testing"
"github.com/go-rod/rod"
)
func (rs *RodSession) verifyIsPublic(t *testing.T, page *rod.Page) {
page.MustElementR("body", "headers")
rs.verifyURLIs(t, page, fmt.Sprintf("%s/headers", PublicBaseURL))
}

View File

@ -1,10 +1,11 @@
package suites
import (
"context"
"testing"
"github.com/go-rod/rod"
)
func (wds *WebDriverSession) verifyIsSecondFactorPage(ctx context.Context, t *testing.T) {
wds.WaitElementLocatedByID(ctx, t, "second-factor-stage")
func (rs *RodSession) verifyIsSecondFactorPage(t *testing.T, page *rod.Page) {
rs.WaitElementLocatedByCSSSelector(t, page, "second-factor-stage")
}

View File

@ -1,10 +1,11 @@
package suites
import (
"context"
"testing"
"github.com/go-rod/rod"
)
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.")
func (rs *RodSession) verifyMailNotificationDisplayed(t *testing.T, page *rod.Page) {
rs.verifyNotificationDisplayed(t, page, "An email has been sent to your address to complete the process.")
}

View File

@ -1,14 +1,14 @@
package suites
import (
"context"
"testing"
"github.com/go-rod/rod"
"github.com/stretchr/testify/assert"
)
func (wds *WebDriverSession) verifyNotificationDisplayed(ctx context.Context, t *testing.T, message string) {
el := wds.WaitElementLocatedByClassName(ctx, t, "notification")
func (rs *RodSession) verifyNotificationDisplayed(t *testing.T, page *rod.Page, message string) {
el, err := page.ElementR(".notification", message)
assert.NoError(t, err)
assert.NotNil(t, el)
wds.WaitElementTextContains(ctx, t, el, message)
}

View File

@ -1,10 +1,11 @@
package suites
import (
"context"
"testing"
"github.com/go-rod/rod"
)
func (wds *WebDriverSession) verifySecretAuthorized(ctx context.Context, t *testing.T) {
wds.WaitElementLocatedByID(ctx, t, "secret")
func (rs *RodSession) verifySecretAuthorized(t *testing.T, page *rod.Page) {
rs.WaitElementLocatedByCSSSelector(t, page, "secret")
}

View File

@ -1,23 +1,13 @@
package suites
import (
"context"
"testing"
"github.com/go-rod/rod"
"github.com/stretchr/testify/require"
"github.com/tebeka/selenium"
)
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
})
require.NoError(t, err)
func (rs *RodSession) verifyURLIs(t *testing.T, page *rod.Page, url string) {
currentURL := page.MustInfo().URL
require.Equal(t, url, currentURL, "they should be equal")
}

View File

@ -1,250 +1,115 @@
package suites
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"time"
log "github.com/sirupsen/logrus"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/launcher"
"github.com/stretchr/testify/require"
"github.com/tebeka/selenium"
"github.com/tebeka/selenium/chrome"
)
// WebDriverSession binding a selenium service and a webdriver.
type WebDriverSession struct {
service *selenium.Service
WebDriver selenium.WebDriver
// RodSession binding a chrome session with devtool protocol.
type RodSession struct {
Launcher *launcher.Launcher
WebDriver *rod.Browser
}
// StartWebDriverWithProxy create a selenium session.
func StartWebDriverWithProxy(proxy string, port int) (*WebDriverSession, error) {
driverPath := os.Getenv("CHROMEDRIVER_PATH")
if driverPath == "" {
driverPath = "/usr/bin/chromedriver"
}
service, err := selenium.NewChromeDriverService(driverPath, port)
if err != nil {
return nil, err
}
// StartRodWithProxy create a rod/chromedp session.
func StartRodWithProxy(proxy string) (*RodSession, error) {
browserPath := os.Getenv("BROWSER_PATH")
if browserPath == "" {
browserPath = "/usr/bin/chromium-browser"
}
chromeCaps := chrome.Capabilities{
Path: browserPath,
}
chromeCaps.Args = append(chromeCaps.Args, "--ignore-certificate-errors")
headless := false
trace := true
motion := 0 * time.Second
if os.Getenv("HEADLESS") != "" {
chromeCaps.Args = append(chromeCaps.Args, "--headless")
chromeCaps.Args = append(chromeCaps.Args, "--no-sandbox")
headless = true
trace = false
motion = 0 * time.Second
}
if proxy != "" {
chromeCaps.Args = append(chromeCaps.Args, fmt.Sprintf("--proxy-server=%s", proxy))
}
l := launcher.New().
Bin(browserPath).
Proxy(proxy).
Headless(headless).
Devtools(true)
url := l.MustLaunch()
caps := selenium.Capabilities{}
caps.AddChrome(chromeCaps)
browser := rod.New().
ControlURL(url).
Trace(trace).
SlowMotion(motion).
MustConnect()
wd, err := selenium.NewRemote(caps, fmt.Sprintf("http://localhost:%d/wd/hub", port))
if err != nil {
_ = service.Stop()
browser.MustIgnoreCertErrors(true)
log.Fatal(err)
}
return &WebDriverSession{
service: service,
WebDriver: wd,
return &RodSession{
Launcher: l,
WebDriver: browser,
}, nil
}
// StartWebDriver create a selenium session.
func StartWebDriver() (*WebDriverSession, error) {
return StartWebDriverWithProxy("", GetWebDriverPort())
// StartRod create a rod/chromedp session.
func StartRod() (*RodSession, error) {
return StartRodWithProxy("")
}
// Stop stop the selenium session.
func (wds *WebDriverSession) Stop() error {
var coverage map[string]interface{}
coverageDir := "../../web/.nyc_output"
time := time.Now()
resp, err := wds.WebDriver.ExecuteScriptRaw("return JSON.stringify(window.__coverage__)", nil)
// Stop stop the rod/chromedp session.
func (rs *RodSession) Stop() error {
err := rs.WebDriver.Close()
if err != nil {
return err
}
err = json.Unmarshal(resp, &coverage)
if err != nil {
return err
}
rs.Launcher.Cleanup()
coverageData := fmt.Sprintf("%s", coverage["value"])
_ = os.MkdirAll(coverageDir, 0775)
err = ioutil.WriteFile(fmt.Sprintf("%s/coverage-%d.json", coverageDir, time.Unix()), []byte(coverageData), 0664) //nolint:gosec
if err != nil {
return err
}
err = filepath.Walk("../../web/.nyc_output", fixCoveragePath)
if err != nil {
return err
}
err = wds.WebDriver.Quit()
if err != nil {
return err
}
return wds.service.Stop()
}
// WithWebdriver run some actions against a webdriver.
func WithWebdriver(fn func(webdriver selenium.WebDriver) error) error {
wds, err := StartWebDriver()
if err != nil {
return err
}
defer wds.Stop() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
return fn(wds.WebDriver)
}
// 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 := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) {
var err error
el, err = driver.FindElement(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
}
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 (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 (wds *WebDriverSession) WaitElementLocatedByTagName(ctx context.Context, t *testing.T, tagName string) selenium.WebElement {
return wds.waitElementLocated(ctx, t, selenium.ByTagName, tagName)
return err
}
// WaitElementLocatedByClassName wait an element is located by class name.
func (wds *WebDriverSession) WaitElementLocatedByClassName(ctx context.Context, t *testing.T, className string) selenium.WebElement {
return wds.waitElementLocated(ctx, t, selenium.ByClassName, className)
}
func (rs *RodSession) WaitElementLocatedByClassName(t *testing.T, page *rod.Page, className string) *rod.Element {
e, err := page.Element("." + className)
require.NoError(t, err)
require.NotNil(t, e)
// 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)
return e
}
// 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)
func (rs *RodSession) WaitElementLocatedByCSSSelector(t *testing.T, page *rod.Page, cssSelector string) *rod.Element {
e, err := page.Element("#" + cssSelector)
require.NoError(t, err)
require.NotNil(t, e)
return e
}
// 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)
func (rs *RodSession) WaitElementsLocatedByCSSSelector(t *testing.T, page *rod.Page, cssSelector string) rod.Elements {
e, err := page.Elements("#" + cssSelector)
require.NoError(t, err)
require.NotNil(t, e)
return e
}
// WaitElementTextContains wait the text of an element contains a pattern.
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()
func (rs *RodSession) waitBodyContains(t *testing.T, page *rod.Page, pattern string) {
text, err := page.MustElementR("body", pattern).Text()
require.NoError(t, err)
require.NotNil(t, text)
if err != nil {
return false, err
}
if strings.Contains(text, pattern) {
err = nil
} else {
err = fmt.Errorf("body does not contain pattern: %s", pattern)
}
return strings.Contains(text, pattern), nil
})
require.NoError(t, err)
}
func (wds *WebDriverSession) waitBodyContains(ctx context.Context, t *testing.T, pattern string) {
err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) {
text, err := wds.WaitElementLocatedByTagName(ctx, t, "body").Text()
if err != nil {
return false, err
}
return strings.Contains(text, pattern), nil
})
require.NoError(t, err)
}

View File

@ -33,7 +33,7 @@ const (
const (
// Hour is an int based representation of the time unit.
Hour = time.Minute * 60
Hour = time.Minute * 60 //nolint: revive
// Day is an int based representation of the time unit.
Day = Hour * 24