2021-05-05 05:06:05 +07:00
|
|
|
package suites
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2022-04-07 12:33:53 +07:00
|
|
|
"regexp"
|
2021-05-05 05:06:05 +07:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
)
|
|
|
|
|
|
|
|
type OIDCScenario struct {
|
2021-11-05 20:14:42 +07:00
|
|
|
*RodSuite
|
2021-05-05 05:06:05 +07:00
|
|
|
secret string
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewOIDCScenario() *OIDCScenario {
|
|
|
|
return &OIDCScenario{
|
2021-11-05 20:14:42 +07:00
|
|
|
RodSuite: new(RodSuite),
|
2021-05-05 05:06:05 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *OIDCScenario) SetupSuite() {
|
2021-11-05 20:14:42 +07:00
|
|
|
browser, err := StartRod()
|
2021-05-05 05:06:05 +07:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2021-11-05 20:14:42 +07:00
|
|
|
s.RodSession = browser
|
2021-05-05 05:06:05 +07:00
|
|
|
|
2021-11-05 20:14:42 +07:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
|
|
defer func() {
|
|
|
|
cancel()
|
|
|
|
s.collectScreenshot(ctx.Err(), s.Page)
|
|
|
|
|
|
|
|
s.collectCoverage(s.Page)
|
|
|
|
s.MustClose()
|
|
|
|
}()
|
2021-05-05 05:06:05 +07:00
|
|
|
|
2021-11-05 20:14:42 +07:00
|
|
|
s.Page = s.doCreateTab(s.T(), HomeBaseURL)
|
|
|
|
s.secret = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, AdminBaseURL)
|
2021-05-05 05:06:05 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *OIDCScenario) TearDownSuite() {
|
2021-11-05 20:14:42 +07:00
|
|
|
err := s.RodSession.Stop()
|
2021-05-05 05:06:05 +07:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *OIDCScenario) SetupTest() {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
2021-11-05 20:14:42 +07:00
|
|
|
defer func() {
|
|
|
|
cancel()
|
|
|
|
s.collectScreenshot(ctx.Err(), s.Page)
|
|
|
|
}()
|
|
|
|
|
|
|
|
s.Page = s.doCreateTab(s.T(), fmt.Sprintf("%s/logout", OIDCBaseURL))
|
|
|
|
s.doVisit(s.T(), s.Context(ctx), HomeBaseURL)
|
|
|
|
s.verifyIsHome(s.T(), s.Context(ctx))
|
|
|
|
}
|
2021-05-05 05:06:05 +07:00
|
|
|
|
2021-11-05 20:14:42 +07:00
|
|
|
func (s *OIDCScenario) TearDownTest() {
|
|
|
|
s.collectCoverage(s.Page)
|
|
|
|
s.MustClose()
|
2021-05-05 05:06:05 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *OIDCScenario) TestShouldAuthorizeAccessToOIDCApp() {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
2021-11-05 20:14:42 +07:00
|
|
|
defer func() {
|
|
|
|
cancel()
|
|
|
|
s.collectScreenshot(ctx.Err(), s.Page)
|
|
|
|
}()
|
2021-05-05 05:06:05 +07:00
|
|
|
|
2021-11-05 20:14:42 +07:00
|
|
|
s.doVisit(s.T(), s.Context(ctx), OIDCBaseURL)
|
|
|
|
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
|
|
|
|
s.doFillLoginPageAndClick(s.T(), s.Context(ctx), "john", "password", false)
|
|
|
|
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
|
|
|
|
s.doValidateTOTP(s.T(), s.Context(ctx), s.secret)
|
2021-05-05 05:06:05 +07:00
|
|
|
|
2021-11-05 20:14:42 +07:00
|
|
|
s.waitBodyContains(s.T(), s.Context(ctx), "Not logged yet...")
|
2021-05-05 05:06:05 +07:00
|
|
|
|
2022-01-31 12:25:15 +07:00
|
|
|
// Search for the 'login' link.
|
2021-11-05 20:14:42 +07:00
|
|
|
err := s.Page.MustSearch("Log in").Click("left")
|
2021-05-05 05:06:05 +07:00
|
|
|
assert.NoError(s.T(), err)
|
|
|
|
|
2021-11-05 20:14:42 +07:00
|
|
|
s.verifyIsConsentPage(s.T(), s.Context(ctx))
|
2022-04-07 12:33:53 +07:00
|
|
|
err = s.WaitElementLocatedByID(s.T(), s.Context(ctx), "accept-button").Click("left")
|
2021-05-05 05:06:05 +07:00
|
|
|
assert.NoError(s.T(), err)
|
|
|
|
|
2022-01-31 12:25:15 +07:00
|
|
|
// Verify that the app is showing the info related to the user stored in the JWT token.
|
2022-04-07 12:33:53 +07:00
|
|
|
|
|
|
|
rUUID := regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
|
|
|
|
rInteger := regexp.MustCompile(`^\d+$`)
|
|
|
|
rBoolean := regexp.MustCompile(`^(true|false)$`)
|
|
|
|
rBase64 := regexp.MustCompile(`^[-_A-Za-z0-9+\\/]+([=]{0,3})$`)
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
desc, elementID, elementText string
|
|
|
|
pattern *regexp.Regexp
|
|
|
|
}{
|
|
|
|
{"welcome", "welcome", "Logged in as john!", nil},
|
|
|
|
{"at_hash", "claim-at_hash", "", rBase64},
|
|
|
|
{"jti", "claim-jti", "", rUUID},
|
|
|
|
{"iat", "claim-iat", "", rInteger},
|
|
|
|
{"nbf", "claim-nbf", "", rInteger},
|
|
|
|
{"rat", "claim-rat", "", rInteger},
|
|
|
|
{"expires", "claim-exp", "", rInteger},
|
|
|
|
{"amr", "claim-amr", "pwd, otp, mfa", nil},
|
|
|
|
{"acr", "claim-acr", "", nil},
|
|
|
|
{"issuer", "claim-iss", "https://login.example.com:8080", nil},
|
|
|
|
{"name", "claim-name", "John Doe", nil},
|
|
|
|
{"preferred_username", "claim-preferred_username", "john", nil},
|
|
|
|
{"groups", "claim-groups", "admins, dev", nil},
|
|
|
|
{"email", "claim-email", "john.doe@authelia.com", nil},
|
|
|
|
{"email_verified", "claim-email_verified", "", rBoolean},
|
|
|
|
}
|
|
|
|
|
|
|
|
var text string
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
s.T().Run(fmt.Sprintf("check_claims/%s", tc.desc), func(t *testing.T) {
|
|
|
|
text, err = s.WaitElementLocatedByID(t, s.Context(ctx), tc.elementID).Text()
|
|
|
|
assert.NoError(t, err)
|
|
|
|
if tc.pattern == nil {
|
|
|
|
assert.Equal(t, tc.elementText, text)
|
|
|
|
} else {
|
|
|
|
assert.Regexp(t, tc.pattern, text)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2021-05-05 05:06:05 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *OIDCScenario) TestShouldDenyConsent() {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
2021-11-05 20:14:42 +07:00
|
|
|
defer func() {
|
|
|
|
cancel()
|
|
|
|
s.collectScreenshot(ctx.Err(), s.Page)
|
|
|
|
}()
|
2021-05-05 05:06:05 +07:00
|
|
|
|
2021-11-05 20:14:42 +07:00
|
|
|
s.doVisit(s.T(), s.Context(ctx), OIDCBaseURL)
|
|
|
|
s.verifyIsFirstFactorPage(s.T(), s.Context(ctx))
|
|
|
|
s.doFillLoginPageAndClick(s.T(), s.Context(ctx), "john", "password", false)
|
|
|
|
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
|
|
|
|
s.doValidateTOTP(s.T(), s.Context(ctx), s.secret)
|
2021-05-05 05:06:05 +07:00
|
|
|
|
2021-11-05 20:14:42 +07:00
|
|
|
s.waitBodyContains(s.T(), s.Context(ctx), "Not logged yet...")
|
2021-05-05 05:06:05 +07:00
|
|
|
|
2022-01-31 12:25:15 +07:00
|
|
|
// Search for the 'login' link.
|
2021-11-05 20:14:42 +07:00
|
|
|
err := s.Page.MustSearch("Log in").Click("left")
|
2021-05-05 05:06:05 +07:00
|
|
|
assert.NoError(s.T(), err)
|
|
|
|
|
2021-11-05 20:14:42 +07:00
|
|
|
s.verifyIsConsentPage(s.T(), s.Context(ctx))
|
2021-05-05 05:06:05 +07:00
|
|
|
|
2022-04-07 12:33:53 +07:00
|
|
|
err = s.WaitElementLocatedByID(s.T(), s.Context(ctx), "deny-button").Click("left")
|
2021-05-05 05:06:05 +07:00
|
|
|
assert.NoError(s.T(), err)
|
|
|
|
|
2022-04-07 12:33:53 +07:00
|
|
|
s.verifyIsOIDC(s.T(), s.Context(ctx), "access_denied", "https://oidc.example.com:8080/error?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request.+Make+sure+that+the+request+you+are+making+is+valid.+Maybe+the+credential+or+request+parameters+you+are+using+are+limited+in+scope+or+otherwise+restricted.&state=random-string-here")
|
|
|
|
|
|
|
|
errorDescription := "The resource owner or authorization server denied the request. Make sure that the request " +
|
|
|
|
"you are making is valid. Maybe the credential or request parameters you are using are limited in scope or " +
|
|
|
|
"otherwise restricted."
|
|
|
|
|
|
|
|
s.verifyIsOIDCErrorPage(s.T(), s.Context(ctx), "access_denied", errorDescription, "",
|
|
|
|
"random-string-here")
|
2021-05-05 05:06:05 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestRunOIDCScenario(t *testing.T) {
|
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("skipping suite test in short mode")
|
|
|
|
}
|
|
|
|
|
|
|
|
suite.Run(t, NewOIDCSuite())
|
|
|
|
}
|