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.
pull/2569/head
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/router v1.4.4
github.com/fasthttp/session/v2 v2.4.4 github.com/fasthttp/session/v2 v2.4.4
github.com/go-ldap/ldap/v3 v3.4.1 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/go-sql-driver/mysql v1.6.0
github.com/golang-jwt/jwt/v4 v4.1.0 github.com/golang-jwt/jwt/v4 v4.1.0
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
@ -30,7 +31,6 @@ require (
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.2.1 github.com/spf13/cobra v1.2.1
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/tebeka/selenium v0.9.9
github.com/tstranex/u2f v1.0.0 github.com/tstranex/u2f v1.0.0
github.com/valyala/fasthttp v1.31.0 github.com/valyala/fasthttp v1.31.0
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b // indirect 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 h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= 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/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/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.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 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= 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-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 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-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/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-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/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/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.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= 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/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/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= 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-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 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= 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.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.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 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.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 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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.16.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw=
github.com/google/go-jsonnet v0.17.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/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 v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 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.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 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 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 h1:C16pk7tQNiH6VlCrtIXL1w8GaOsi1X3W8KDkE1BuYd4=
github.com/tidwall/gjson v1.11.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.11.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 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/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/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/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.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/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= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

View File

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

View File

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

View File

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

View File

@ -1,13 +1,14 @@
package suites package suites
import ( import (
"context"
"testing" "testing"
"github.com/go-rod/rod"
) )
func (wds *WebDriverSession) doRegisterThenLogout(ctx context.Context, t *testing.T, username, password string) string { func (rs *RodSession) doRegisterThenLogout(t *testing.T, page *rod.Page, username, password string) string {
secret := wds.doLoginAndRegisterTOTP(ctx, t, username, password, false) secret := rs.doLoginAndRegisterTOTP(t, page, username, password, false)
wds.doLogout(ctx, t) rs.doLogout(t, page)
return secret return secret
} }

View File

@ -1,52 +1,60 @@
package suites package suites
import ( import (
"context"
"testing" "testing"
"time"
"github.com/go-rod/rod"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func (wds *WebDriverSession) doInitiatePasswordReset(ctx context.Context, t *testing.T, username string) { func (rs *RodSession) doInitiatePasswordReset(t *testing.T, page *rod.Page, username string) {
err := wds.WaitElementLocatedByID(ctx, t, "reset-password-button").Click() err := rs.WaitElementLocatedByCSSSelector(t, page, "reset-password-button").Click("left")
require.NoError(t, err) require.NoError(t, err)
// Fill in username // 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) require.NoError(t, err)
// And click on the reset button // 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) 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) 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) 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) 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) require.NoError(t, err)
} }
func (wds *WebDriverSession) doSuccessfullyCompletePasswordReset(ctx context.Context, t *testing.T, newPassword1, newPassword2 string) { func (rs *RodSession) doSuccessfullyCompletePasswordReset(t *testing.T, page *rod.Page, newPassword1, newPassword2 string) {
wds.doCompletePasswordReset(ctx, t, newPassword1, newPassword2) rs.doCompletePasswordReset(t, page, newPassword1, newPassword2)
wds.verifyIsFirstFactorPage(ctx, t) rs.verifyIsFirstFactorPage(t, page)
} }
func (wds *WebDriverSession) doUnsuccessfulPasswordReset(ctx context.Context, t *testing.T, newPassword1, newPassword2 string) { func (rs *RodSession) doUnsuccessfulPasswordReset(t *testing.T, page *rod.Page, newPassword1, newPassword2 string) {
wds.doCompletePasswordReset(ctx, t, newPassword1, newPassword2) 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) { func (rs *RodSession) doResetPassword(t *testing.T, page *rod.Page, username, newPassword1, newPassword2 string, unsuccessful bool) {
wds.doInitiatePasswordReset(ctx, t, username) rs.doInitiatePasswordReset(t, page, username)
// then wait for the "email sent notification" // then wait for the "email sent notification"
wds.verifyMailNotificationDisplayed(ctx, t) rs.verifyMailNotificationDisplayed(t, page)
if unsuccessful { if unsuccessful {
wds.doUnsuccessfulPasswordReset(ctx, t, newPassword1, newPassword2) rs.doUnsuccessfulPasswordReset(t, page, newPassword1, newPassword2)
} else { } else {
wds.doSuccessfullyCompletePasswordReset(ctx, t, newPassword1, newPassword2) rs.doSuccessfullyCompletePasswordReset(t, page, newPassword1, newPassword2)
} }
} }

View File

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

View File

@ -1,28 +1,36 @@
package suites package suites
import ( import (
"context"
"fmt" "fmt"
"testing" "testing"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/proto"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func (wds *WebDriverSession) doVisit(t *testing.T, url string) { func (rs *RodSession) doCreateTab(t *testing.T, url string) *rod.Page {
err := wds.WebDriver.Get(url) 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) assert.NoError(t, err)
} }
func (wds *WebDriverSession) doVisitAndVerifyOneFactorStep(ctx context.Context, t *testing.T, url string) { func (rs *RodSession) doVisitAndVerifyOneFactorStep(t *testing.T, page *rod.Page, url string) {
wds.doVisit(t, url) rs.doVisit(t, page, url)
wds.verifyIsFirstFactorPage(ctx, t) 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 := "" suffix := ""
if targetURL != "" { if targetURL != "" {
suffix = fmt.Sprintf("?rd=%s", 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" var AutheliaBaseURL = "https://authelia.example.com:9091"
const stringTrue = "true" const stringTrue = "true"
const defaultChromeDriverPort = "4444"
const testUsername = "john" const testUsername = "john"
const testPassword = "password" 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 log stdout format raw local0 debug
defaults defaults
default-server init-addr none
mode http mode http
log global log global
option httplog option httplog
option forwardfor option forwardfor
resolvers docker
nameserver ip 127.0.0.11:53
frontend fe_api frontend fe_api
bind *:8081 ssl crt /usr/local/etc/haproxy/haproxy.pem bind *:8081 ssl crt /usr/local/etc/haproxy/haproxy.pem
@ -63,13 +67,13 @@ backend be_auth_request
listen be_auth_request_proxy listen be_auth_request_proxy
mode http mode http
bind 127.0.0.1:8085 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 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 backend fe_authelia
server authelia-frontend authelia-frontend:3000 server authelia-frontend authelia-frontend:3000 resolvers docker
backend be_httpbin backend be_httpbin
acl remote_user_exist var(req.auth_response_header.remote_user) -m found 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-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 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 backend be_mail
server smtp-backend smtp:1080 server smtp-backend smtp:1080 resolvers docker
backend be_protected 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" "log"
"time" "time"
"github.com/tebeka/selenium"
"github.com/authelia/authelia/v4/internal/utils" "github.com/authelia/authelia/v4/internal/utils"
) )
type AvailableMethodsScenario struct { type AvailableMethodsScenario struct {
*SeleniumSuite *RodSuite
methods []string methods []string
} }
func NewAvailableMethodsScenario(methods []string) *AvailableMethodsScenario { func NewAvailableMethodsScenario(methods []string) *AvailableMethodsScenario {
return &AvailableMethodsScenario{ return &AvailableMethodsScenario{
SeleniumSuite: new(SeleniumSuite), RodSuite: new(RodSuite),
methods: methods, methods: methods,
} }
} }
func (s *AvailableMethodsScenario) SetupSuite() { func (s *AvailableMethodsScenario) SetupSuite() {
wds, err := StartWebDriver() browser, err := StartRod()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
s.SeleniumSuite.WebDriverSession = wds s.RodSession = browser
} }
func (s *AvailableMethodsScenario) TearDownSuite() { func (s *AvailableMethodsScenario) TearDownSuite() {
err := s.WebDriverSession.Stop() err := s.RodSession.Stop()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -42,26 +40,30 @@ func (s *AvailableMethodsScenario) TearDownSuite() {
} }
func (s *AvailableMethodsScenario) SetupTest() { func (s *AvailableMethodsScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) s.Page = s.doCreateTab(s.T(), HomeBaseURL)
defer cancel() s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T()) func (s *AvailableMethodsScenario) TearDownTest() {
s.doVisit(s.T(), HomeBaseURL) s.collectCoverage(s.Page)
s.verifyIsHome(ctx, s.T()) s.MustClose()
} }
func (s *AvailableMethodsScenario) TestShouldCheckAvailableMethods() { func (s *AvailableMethodsScenario) TestShouldCheckAvailableMethods() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) 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") methodsButton := s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "methods-button")
err := methodsButton.Click() err := methodsButton.Click("left")
s.Assert().NoError(err) s.Assert().NoError(err)
methodsDialog := s.WaitElementLocatedByID(ctx, s.T(), "methods-dialog") methodsDialog := s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "methods-dialog")
options, err := methodsDialog.FindElements(selenium.ByClassName, "method-option") options, err := methodsDialog.Elements(".method-option")
s.Assert().NoError(err) s.Assert().NoError(err)
s.Assert().Len(options, len(s.methods)) s.Assert().Len(options, len(s.methods))

View File

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

View File

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

View File

@ -11,57 +11,62 @@ import (
) )
type DefaultRedirectionURLScenario struct { type DefaultRedirectionURLScenario struct {
*SeleniumSuite *RodSuite
secret string secret string
} }
func NewDefaultRedirectionURLScenario() *DefaultRedirectionURLScenario { func NewDefaultRedirectionURLScenario() *DefaultRedirectionURLScenario {
return &DefaultRedirectionURLScenario{ return &DefaultRedirectionURLScenario{
SeleniumSuite: new(SeleniumSuite), RodSuite: new(RodSuite),
} }
} }
func (drus *DefaultRedirectionURLScenario) SetupSuite() { func (s *DefaultRedirectionURLScenario) SetupSuite() {
wds, err := StartWebDriver() browser, err := StartRod()
if err != nil { if err != nil {
log.Fatal(err) 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) 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) 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() { s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
err := drus.WebDriverSession.Stop() 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 { s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, s.secret, "")
log.Fatal(err) s.verifyIsHome(s.T(), s.Page)
}
}
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+"/")
} }
func TestShouldRunDefaultRedirectionURLScenario(t *testing.T) { func TestShouldRunDefaultRedirectionURLScenario(t *testing.T) {

View File

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

View File

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

View File

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

View File

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

View File

@ -10,27 +10,27 @@ import (
) )
type RedirectionCheckScenario struct { type RedirectionCheckScenario struct {
*SeleniumSuite *RodSuite
} }
func NewRedirectionCheckScenario() *RedirectionCheckScenario { func NewRedirectionCheckScenario() *RedirectionCheckScenario {
return &RedirectionCheckScenario{ return &RedirectionCheckScenario{
SeleniumSuite: new(SeleniumSuite), RodSuite: new(RodSuite),
} }
} }
func (s *RedirectionCheckScenario) SetupSuite() { func (s *RedirectionCheckScenario) SetupSuite() {
wds, err := StartWebDriver() browser, err := StartRod()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
s.WebDriverSession = wds s.RodSession = browser
} }
func (s *RedirectionCheckScenario) TearDownSuite() { func (s *RedirectionCheckScenario) TearDownSuite() {
err := s.WebDriverSession.Stop() err := s.RodSession.Stop()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -38,12 +38,13 @@ func (s *RedirectionCheckScenario) TearDownSuite() {
} }
func (s *RedirectionCheckScenario) SetupTest() { func (s *RedirectionCheckScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) s.Page = s.doCreateTab(s.T(), HomeBaseURL)
defer cancel() s.verifyIsHome(s.T(), s.Page)
}
s.doLogout(ctx, s.T()) func (s *RedirectionCheckScenario) TearDownTest() {
s.doVisit(s.T(), HomeBaseURL) s.collectCoverage(s.Page)
s.verifyIsHome(ctx, s.T()) s.MustClose()
} }
var redirectionAuthorizations = map[string]bool{ var redirectionAuthorizations = map[string]bool{
@ -59,21 +60,24 @@ var redirectionAuthorizations = map[string]bool{
func (s *RedirectionCheckScenario) TestShouldRedirectOnLoginOnlyWhenDomainIsSafe() { func (s *RedirectionCheckScenario) TestShouldRedirectOnLoginOnlyWhenDomainIsSafe() {
ctx, cancel := context.WithTimeout(context.Background(), 35*time.Second) 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 { for url, redirected := range redirectionAuthorizations {
s.T().Run(url, func(t *testing.T) { 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 { if redirected {
s.verifySecretAuthorized(ctx, t) s.verifySecretAuthorized(t, s.Context(ctx))
} else { } 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() { func (s *RedirectionCheckScenario) TestShouldRedirectOnLogoutOnlyWhenDomainIsSafe() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 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 { for url, success := range logoutRedirectionURLs {
s.T().Run(url, func(t *testing.T) { 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 { type RedirectionURLScenario struct {
*SeleniumSuite *RodSuite
} }
func NewRedirectionURLScenario() *RedirectionURLScenario { func NewRedirectionURLScenario() *RedirectionURLScenario {
return &RedirectionURLScenario{ return &RedirectionURLScenario{
SeleniumSuite: new(SeleniumSuite), RodSuite: new(RodSuite),
} }
} }
func (rus *RedirectionURLScenario) SetupSuite() { func (s *RedirectionURLScenario) SetupSuite() {
wds, err := StartWebDriver() browser, err := StartRod()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
rus.WebDriverSession = wds s.RodSession = browser
} }
func (rus *RedirectionURLScenario) TearDownSuite() { func (s *RedirectionURLScenario) TearDownSuite() {
err := rus.WebDriverSession.Stop() err := s.RodSession.Stop()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
func (rus *RedirectionURLScenario) SetupTest() { func (s *RedirectionURLScenario) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) s.Page = s.doCreateTab(s.T(), HomeBaseURL)
defer cancel() s.verifyIsHome(s.T(), s.Page)
rus.doLogout(ctx, rus.T())
rus.doVisit(rus.T(), HomeBaseURL)
rus.verifyIsHome(ctx, rus.T())
} }
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) 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) targetURL := fmt.Sprintf("%s/secret.html?myparam=test", SingleFactorBaseURL)
rus.doLoginOneFactor(ctx, rus.T(), "john", "password", false, targetURL) s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, targetURL)
rus.verifySecretAuthorized(ctx, rus.T()) s.verifySecretAuthorized(s.T(), s.Context(ctx))
rus.verifyURLIs(ctx, rus.T(), targetURL) s.verifyURLIs(s.T(), s.Context(ctx), targetURL)
} }
func TestRedirectionURLScenario(t *testing.T) { func TestRedirectionURLScenario(t *testing.T) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,40 +11,53 @@ import (
) )
type BypassAllWebDriverSuite struct { type BypassAllWebDriverSuite struct {
*SeleniumSuite *RodSuite
} }
func NewBypassAllWebDriverSuite() *BypassAllWebDriverSuite { func NewBypassAllWebDriverSuite() *BypassAllWebDriverSuite {
return &BypassAllWebDriverSuite{SeleniumSuite: new(SeleniumSuite)} return &BypassAllWebDriverSuite{RodSuite: new(RodSuite)}
} }
func (s *BypassAllWebDriverSuite) SetupSuite() { func (s *BypassAllWebDriverSuite) SetupSuite() {
wds, err := StartWebDriver() browser, err := StartRod()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
s.WebDriverSession = wds s.RodSession = browser
} }
func (s *BypassAllWebDriverSuite) TearDownSuite() { func (s *BypassAllWebDriverSuite) TearDownSuite() {
err := s.WebDriverSession.Stop() err := s.RodSession.Stop()
if err != nil { if err != nil {
log.Fatal(err) 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() { func (s *BypassAllWebDriverSuite) TestShouldAccessPublicResource() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 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.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/secret.html", AdminBaseURL))
s.verifySecretAuthorized(ctx, s.T()) s.verifySecretAuthorized(s.T(), s.Context(ctx))
s.doVisit(s.T(), fmt.Sprintf("%s/secret.html", PublicBaseURL)) s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/secret.html", PublicBaseURL))
s.verifySecretAuthorized(ctx, s.T()) s.verifySecretAuthorized(s.T(), s.Context(ctx))
} }
type BypassAllSuite struct { type BypassAllSuite struct {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,11 +10,11 @@ import (
) )
type Traefik2Suite struct { type Traefik2Suite struct {
*SeleniumSuite *RodSuite
} }
func NewTraefik2Suite() *Traefik2Suite { func NewTraefik2Suite() *Traefik2Suite {
return &Traefik2Suite{SeleniumSuite: new(SeleniumSuite)} return &Traefik2Suite{RodSuite: new(RodSuite)}
} }
func (s *Traefik2Suite) TestOneFactorScenario() { func (s *Traefik2Suite) TestOneFactorScenario() {
@ -30,31 +30,34 @@ func (s *Traefik2Suite) TestCustomHeaders() {
} }
func (s *Traefik2Suite) TestShouldKeepSessionAfterRedisRestart() { func (s *Traefik2Suite) TestShouldKeepSessionAfterRedisRestart() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer cancel()
wds, err := StartWebDriver()
s.Require().NoError(err)
defer func() { 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) 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)) s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, "")
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))
err = traefik2DockerEnvironment.Restart("redis") err = traefik2DockerEnvironment.Restart("redis")
s.Require().NoError(err) s.Require().NoError(err)
time.Sleep(5 * time.Second) s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/secret.html", SecureBaseURL))
s.verifySecretAuthorized(s.T(), s.Context(ctx))
wds.doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL))
wds.verifySecretAuthorized(ctx, s.T())
} }
func TestTraefik2Suite(t *testing.T) { func TestTraefik2Suite(t *testing.T) {

View File

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

View File

@ -1,15 +1,16 @@
package suites package suites
import ( import (
"github.com/go-rod/rod"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/tebeka/selenium"
) )
// SeleniumSuite is a selenium suite. // RodSuite is a go-rod suite.
type SeleniumSuite struct { type RodSuite struct {
suite.Suite suite.Suite
*WebDriverSession *RodSession
*rod.Page
} }
// CommandSuite is a command line interface suite. // CommandSuite is a command line interface suite.
@ -21,8 +22,3 @@ type CommandSuite struct {
*DockerEnvironment *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 package suites
import ( import (
"context"
"fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "runtime"
"strings" "strings"
"time"
"github.com/go-rod/rod"
) )
// GetLoginBaseURL returns the URL of the login portal and the path prefix if specified. // GetLoginBaseURL returns the URL of the login portal and the path prefix if specified.
@ -17,16 +23,51 @@ func GetLoginBaseURL() string {
return LoginBaseURL return LoginBaseURL
} }
// GetWebDriverPort returns the port to initialize the webdriver with. func (rs *RodSession) collectCoverage(page *rod.Page) {
func GetWebDriverPort() int { coverageDir := "../../web/.nyc_output"
driverPort := os.Getenv("CHROMEDRIVER_PORT") now := time.Now()
if driverPort == "" {
driverPort = defaultChromeDriverPort 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 { func fixCoveragePath(path string, file os.FileInfo, err error) error {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,13 @@
package suites package suites
import ( import (
"context"
"fmt" "fmt"
"testing" "testing"
"github.com/go-rod/rod"
) )
func (wds *WebDriverSession) verifyIsHome(ctx context.Context, t *testing.T) { func (rs *RodSession) verifyIsHome(t *testing.T, page *rod.Page) {
wds.verifyURLIs(ctx, t, fmt.Sprintf("%s/", HomeBaseURL)) 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 package suites
import ( import (
"context"
"testing" "testing"
"github.com/go-rod/rod"
) )
func (wds *WebDriverSession) verifyIsSecondFactorPage(ctx context.Context, t *testing.T) { func (rs *RodSession) verifyIsSecondFactorPage(t *testing.T, page *rod.Page) {
wds.WaitElementLocatedByID(ctx, t, "second-factor-stage") rs.WaitElementLocatedByCSSSelector(t, page, "second-factor-stage")
} }

View File

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

View File

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

View File

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

View File

@ -1,23 +1,13 @@
package suites package suites
import ( import (
"context"
"testing" "testing"
"github.com/go-rod/rod"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tebeka/selenium"
) )
func (wds *WebDriverSession) verifyURLIs(ctx context.Context, t *testing.T, url string) { func (rs *RodSession) verifyURLIs(t *testing.T, page *rod.Page, url string) {
err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) { currentURL := page.MustInfo().URL
currentURL, err := driver.CurrentURL() require.Equal(t, url, currentURL, "they should be equal")
if err != nil {
return false, err
}
return currentURL == url, nil
})
require.NoError(t, err)
} }

View File

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

View File

@ -33,7 +33,7 @@ const (
const ( const (
// Hour is an int based representation of the time unit. // 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 is an int based representation of the time unit.
Day = Hour * 24 Day = Hour * 24