mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-01-08 15:21:55 +00:00
Replace agouti and chromedriver with chromedp across the whole project
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package integration
|
||||
|
||||
@@ -280,7 +280,7 @@ func runPinnipedLoginOIDC(
|
||||
sessionCachePath := testutil.TempDir(t) + "/sessions.yaml"
|
||||
|
||||
// Start the browser driver.
|
||||
page := browsertest.Open(t)
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
|
||||
// Start the CLI running the "login oidc [...]" command with stdout/stderr connected to pipes.
|
||||
cmd := oidcLoginCommand(ctx, t, pinnipedExe, sessionCachePath)
|
||||
@@ -334,22 +334,21 @@ func runPinnipedLoginOIDC(
|
||||
case loginURL = <-loginURLChan:
|
||||
}
|
||||
t.Logf("navigating to login page")
|
||||
require.NoError(t, page.Navigate(loginURL))
|
||||
browser.Navigate(t, loginURL)
|
||||
|
||||
// Expect to be redirected to the upstream provider and log in.
|
||||
browsertest.LoginToUpstreamOIDC(t, page, env.CLIUpstreamOIDC)
|
||||
browsertest.LoginToUpstreamOIDC(t, browser, env.CLIUpstreamOIDC)
|
||||
|
||||
// Expect to be redirected to the localhost callback.
|
||||
t.Logf("waiting for redirect to callback")
|
||||
callbackURLPattern := regexp.MustCompile(`\A` + regexp.QuoteMeta(env.CLIUpstreamOIDC.CallbackURL) + `(\?.+)?\z`)
|
||||
browsertest.WaitForURL(t, page, callbackURLPattern)
|
||||
browser.WaitForURL(t, callbackURLPattern)
|
||||
|
||||
// Wait for the "pre" element that gets rendered for a `text/plain` page, and
|
||||
// assert that it contains the success message.
|
||||
t.Logf("verifying success page")
|
||||
browsertest.WaitForVisibleElements(t, page, "pre")
|
||||
msg, err := page.First("pre").Text()
|
||||
require.NoError(t, err)
|
||||
browser.WaitForVisibleElements(t, "pre")
|
||||
msg := browser.TextOfFirstMatch(t, "pre")
|
||||
require.Equal(t, "you have been logged in and may now close this tab", msg)
|
||||
|
||||
// Expect the CLI to output an ExecCredential in JSON format.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package integration
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/creack/pty"
|
||||
"github.com/sclevine/agouti"
|
||||
"github.com/stretchr/testify/require"
|
||||
authorizationv1 "k8s.io/api/authorization/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -123,7 +122,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests
|
||||
|
||||
// Start a fresh browser driver because we don't want to share cookies between the various tests in this file.
|
||||
page := browsertest.Open(t)
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
|
||||
expectedUsername := env.SupervisorUpstreamOIDC.Username
|
||||
expectedGroups := env.SupervisorUpstreamOIDC.ExpectedGroups
|
||||
@@ -177,18 +176,18 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
kubectlCmd.Env = append(os.Environ(), env.ProxyEnv()...)
|
||||
|
||||
// Run the kubectl command, wait for the Pinniped CLI to print the authorization URL, and open it in the browser.
|
||||
kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, page)
|
||||
kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, browser)
|
||||
|
||||
// Confirm that we got to the upstream IDP's login page, fill out the form, and submit the form.
|
||||
browsertest.LoginToUpstreamOIDC(t, page, env.SupervisorUpstreamOIDC)
|
||||
browsertest.LoginToUpstreamOIDC(t, browser, env.SupervisorUpstreamOIDC)
|
||||
|
||||
// Expect to be redirected to the downstream callback which is serving the form_post HTML.
|
||||
t.Logf("waiting for response page %s", downstream.Spec.Issuer)
|
||||
browsertest.WaitForURL(t, page, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer)))
|
||||
browser.WaitForURL(t, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer)))
|
||||
|
||||
// The response page should have done the background fetch() and POST'ed to the CLI's callback.
|
||||
// It should now be in the "success" state.
|
||||
formpostExpectSuccessState(t, page)
|
||||
formpostExpectSuccessState(t, browser)
|
||||
|
||||
requireKubectlGetNamespaceOutput(t, env, waitForKubectlOutput(t, kubectlOutputChan))
|
||||
|
||||
@@ -204,7 +203,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests
|
||||
|
||||
// Start a fresh browser driver because we don't want to share cookies between the various tests in this file.
|
||||
page := browsertest.Open(t)
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
|
||||
expectedUsername := env.SupervisorUpstreamOIDC.Username
|
||||
expectedGroups := env.SupervisorUpstreamOIDC.ExpectedGroups
|
||||
@@ -258,18 +257,18 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
kubectlCmd.Env = append(os.Environ(), env.ProxyEnv()...)
|
||||
|
||||
// Run the kubectl command, wait for the Pinniped CLI to print the authorization URL, and open it in the browser.
|
||||
kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, page)
|
||||
kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, browser)
|
||||
|
||||
// Confirm that we got to the upstream IDP's login page, fill out the form, and submit the form.
|
||||
browsertest.LoginToUpstreamOIDC(t, page, env.SupervisorUpstreamOIDC)
|
||||
browsertest.LoginToUpstreamOIDC(t, browser, env.SupervisorUpstreamOIDC)
|
||||
|
||||
// Expect to be redirected to the downstream callback which is serving the form_post HTML.
|
||||
t.Logf("waiting for response page %s", downstream.Spec.Issuer)
|
||||
browsertest.WaitForURL(t, page, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer)))
|
||||
browser.WaitForURL(t, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer)))
|
||||
|
||||
// The response page should have done the background fetch() and POST'ed to the CLI's callback.
|
||||
// It should now be in the "success" state.
|
||||
formpostExpectSuccessState(t, page)
|
||||
formpostExpectSuccessState(t, browser)
|
||||
|
||||
requireKubectlGetNamespaceOutput(t, env, waitForKubectlOutput(t, kubectlOutputChan))
|
||||
|
||||
@@ -288,7 +287,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests
|
||||
|
||||
// Start a fresh browser driver because we don't want to share cookies between the various tests in this file.
|
||||
page := browsertest.Open(t)
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
|
||||
expectedUsername := env.SupervisorUpstreamOIDC.Username
|
||||
expectedGroups := env.SupervisorUpstreamOIDC.ExpectedGroups
|
||||
@@ -363,20 +362,20 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
require.NotEmptyf(t, loginURL, "didn't find login URL in output: %s", output)
|
||||
|
||||
t.Logf("navigating to login page")
|
||||
require.NoError(t, page.Navigate(loginURL))
|
||||
browser.Navigate(t, loginURL)
|
||||
|
||||
// Expect to be redirected to the upstream provider and log in.
|
||||
browsertest.LoginToUpstreamOIDC(t, page, env.SupervisorUpstreamOIDC)
|
||||
browsertest.LoginToUpstreamOIDC(t, browser, env.SupervisorUpstreamOIDC)
|
||||
|
||||
// Expect to be redirected to the downstream callback which is serving the form_post HTML.
|
||||
t.Logf("waiting for response page %s", downstream.Spec.Issuer)
|
||||
browsertest.WaitForURL(t, page, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer)))
|
||||
browser.WaitForURL(t, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer)))
|
||||
|
||||
// The response page should have failed to automatically post, and should now be showing the manual instructions.
|
||||
authCode := formpostExpectManualState(t, page)
|
||||
authCode := formpostExpectManualState(t, browser)
|
||||
|
||||
// Enter the auth code in the waiting prompt, followed by a newline.
|
||||
t.Logf("'manually' pasting authorization code %q to waiting prompt", authCode)
|
||||
t.Logf("'manually' pasting authorization code with length %d to waiting prompt", len(authCode))
|
||||
_, err = ptyFile.WriteString(authCode + "\n")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -399,7 +398,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests
|
||||
|
||||
// Start a fresh browser driver because we don't want to share cookies between the various tests in this file.
|
||||
page := browsertest.Open(t)
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
|
||||
expectedUsername := env.SupervisorUpstreamOIDC.Username
|
||||
expectedGroups := env.SupervisorUpstreamOIDC.ExpectedGroups
|
||||
@@ -488,20 +487,20 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
require.NotEmptyf(t, loginURL, "didn't find login URL in output: %s", output)
|
||||
|
||||
t.Logf("navigating to login page")
|
||||
require.NoError(t, page.Navigate(loginURL))
|
||||
browser.Navigate(t, loginURL)
|
||||
|
||||
// Expect to be redirected to the upstream provider and log in.
|
||||
browsertest.LoginToUpstreamOIDC(t, page, env.SupervisorUpstreamOIDC)
|
||||
browsertest.LoginToUpstreamOIDC(t, browser, env.SupervisorUpstreamOIDC)
|
||||
|
||||
// Expect to be redirected to the downstream callback which is serving the form_post HTML.
|
||||
t.Logf("waiting for response page %s", downstream.Spec.Issuer)
|
||||
browsertest.WaitForURL(t, page, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer)))
|
||||
browser.WaitForURL(t, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer)))
|
||||
|
||||
// The response page should have failed to automatically post, and should now be showing the manual instructions.
|
||||
authCode := formpostExpectManualState(t, page)
|
||||
authCode := formpostExpectManualState(t, browser)
|
||||
|
||||
// Enter the auth code in the waiting prompt, followed by a newline.
|
||||
t.Logf("'manually' pasting authorization code %q to waiting prompt", authCode)
|
||||
t.Logf("'manually' pasting authorization code with length %d to waiting prompt", len(authCode))
|
||||
_, err = ptyFile.WriteString(authCode + "\n")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1002,7 +1001,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests
|
||||
|
||||
// Start a fresh browser driver because we don't want to share cookies between the various tests in this file.
|
||||
page := browsertest.Open(t)
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
|
||||
expectedUsername := env.SupervisorUpstreamLDAP.TestUserMailAttributeValue
|
||||
expectedGroups := env.SupervisorUpstreamLDAP.TestUserDirectGroupsDNs
|
||||
@@ -1029,13 +1028,13 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
kubectlCmd.Env = append(os.Environ(), env.ProxyEnv()...)
|
||||
|
||||
// Run the kubectl command, wait for the Pinniped CLI to print the authorization URL, and open it in the browser.
|
||||
kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, page)
|
||||
kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, browser)
|
||||
|
||||
// Confirm that we got to the Supervisor's login page, fill out the form, and submit the form.
|
||||
browsertest.LoginToUpstreamLDAP(t, page, downstream.Spec.Issuer,
|
||||
browsertest.LoginToUpstreamLDAP(t, browser, downstream.Spec.Issuer,
|
||||
expectedUsername, env.SupervisorUpstreamLDAP.TestUserPassword)
|
||||
|
||||
formpostExpectSuccessState(t, page)
|
||||
formpostExpectSuccessState(t, browser)
|
||||
|
||||
requireKubectlGetNamespaceOutput(t, env, waitForKubectlOutput(t, kubectlOutputChan))
|
||||
|
||||
@@ -1052,7 +1051,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests
|
||||
|
||||
// Start a fresh browser driver because we don't want to share cookies between the various tests in this file.
|
||||
page := browsertest.Open(t)
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
|
||||
expectedUsername := env.SupervisorUpstreamActiveDirectory.TestUserPrincipalNameValue
|
||||
expectedGroups := env.SupervisorUpstreamActiveDirectory.TestUserIndirectGroupsSAMAccountPlusDomainNames
|
||||
@@ -1079,13 +1078,13 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
kubectlCmd.Env = append(os.Environ(), env.ProxyEnv()...)
|
||||
|
||||
// Run the kubectl command, wait for the Pinniped CLI to print the authorization URL, and open it in the browser.
|
||||
kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, page)
|
||||
kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, browser)
|
||||
|
||||
// Confirm that we got to the Supervisor's login page, fill out the form, and submit the form.
|
||||
browsertest.LoginToUpstreamLDAP(t, page, downstream.Spec.Issuer,
|
||||
browsertest.LoginToUpstreamLDAP(t, browser, downstream.Spec.Issuer,
|
||||
expectedUsername, env.SupervisorUpstreamActiveDirectory.TestUserPassword)
|
||||
|
||||
formpostExpectSuccessState(t, page)
|
||||
formpostExpectSuccessState(t, browser)
|
||||
|
||||
requireKubectlGetNamespaceOutput(t, env, waitForKubectlOutput(t, kubectlOutputChan))
|
||||
|
||||
@@ -1102,7 +1101,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests
|
||||
|
||||
// Start a fresh browser driver because we don't want to share cookies between the various tests in this file.
|
||||
page := browsertest.Open(t)
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
|
||||
expectedUsername := env.SupervisorUpstreamLDAP.TestUserMailAttributeValue
|
||||
expectedGroups := env.SupervisorUpstreamLDAP.TestUserDirectGroupsDNs
|
||||
@@ -1135,13 +1134,13 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
kubectlCmd.Env = append(os.Environ(), env.ProxyEnv()...)
|
||||
|
||||
// Run the kubectl command, wait for the Pinniped CLI to print the authorization URL, and open it in the browser.
|
||||
kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, page)
|
||||
kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, browser)
|
||||
|
||||
// Confirm that we got to the Supervisor's login page, fill out the form, and submit the form.
|
||||
browsertest.LoginToUpstreamLDAP(t, page, downstream.Spec.Issuer,
|
||||
browsertest.LoginToUpstreamLDAP(t, browser, downstream.Spec.Issuer,
|
||||
expectedUsername, env.SupervisorUpstreamLDAP.TestUserPassword)
|
||||
|
||||
formpostExpectSuccessState(t, page)
|
||||
formpostExpectSuccessState(t, browser)
|
||||
|
||||
requireKubectlGetNamespaceOutput(t, env, waitForKubectlOutput(t, kubectlOutputChan))
|
||||
|
||||
@@ -1149,7 +1148,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func startKubectlAndOpenAuthorizationURLInBrowser(testCtx context.Context, t *testing.T, kubectlCmd *exec.Cmd, page *agouti.Page) chan string {
|
||||
func startKubectlAndOpenAuthorizationURLInBrowser(testCtx context.Context, t *testing.T, kubectlCmd *exec.Cmd, b *browsertest.Browser) chan string {
|
||||
// Wrap the stdout and stderr pipes with TeeReaders which will copy each incremental read to an
|
||||
// in-memory buffer, so we can have the full output available to us at the end.
|
||||
originalStderrPipe, err := kubectlCmd.StderrPipe()
|
||||
@@ -1226,7 +1225,7 @@ func startKubectlAndOpenAuthorizationURLInBrowser(testCtx context.Context, t *te
|
||||
case loginURL = <-loginURLChan:
|
||||
}
|
||||
t.Logf("navigating to login page: %q", loginURL)
|
||||
require.NoError(t, page.Navigate(loginURL))
|
||||
b.Navigate(t, loginURL)
|
||||
|
||||
return kubectlOutputChan
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package integration
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
|
||||
"github.com/ory/fosite"
|
||||
"github.com/ory/fosite/token/hmac"
|
||||
"github.com/sclevine/agouti"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -33,24 +32,25 @@ func TestFormPostHTML_Browser_Parallel(t *testing.T) {
|
||||
// Run a mock callback handler, simulating the one running in the CLI.
|
||||
callbackURL, expectCallback := formpostCallbackServer(t)
|
||||
|
||||
// Open a single browser for all subtests to use (in sequence).
|
||||
page := browsertest.Open(t)
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
|
||||
// Serve the form_post template with successful parameters.
|
||||
responseParams := formpostRandomParams(t)
|
||||
formpostInitiate(t, page, formpostTemplateServer(t, callbackURL, responseParams))
|
||||
formpostInitiate(t, browser, formpostTemplateServer(t, callbackURL, responseParams))
|
||||
|
||||
// Now we handle the callback and assert that we got what we expected. This should transition
|
||||
// the UI into the success state.
|
||||
expectCallback(t, responseParams)
|
||||
formpostExpectSuccessState(t, page)
|
||||
formpostExpectSuccessState(t, browser)
|
||||
})
|
||||
|
||||
t.Run("callback server error", func(t *testing.T) {
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
|
||||
// Serve the form_post template with a redirect URI that will return an HTTP 500 response.
|
||||
responseParams := formpostRandomParams(t)
|
||||
formpostInitiate(t, page, formpostTemplateServer(t, callbackURL+"?fail=500", responseParams))
|
||||
formpostInitiate(t, browser, formpostTemplateServer(t, callbackURL+"?fail=500", responseParams))
|
||||
|
||||
// Now we handle the callback and assert that we got what we expected.
|
||||
expectCallback(t, responseParams)
|
||||
@@ -66,13 +66,15 @@ func TestFormPostHTML_Browser_Parallel(t *testing.T) {
|
||||
// In the future, we could change the Javascript code to use mode 'cors'
|
||||
// because we have upgraded our CLI callback endpoint to handle CORS,
|
||||
// and then we could change this to formpostExpectManualState().
|
||||
formpostExpectSuccessState(t, page)
|
||||
formpostExpectSuccessState(t, browser)
|
||||
})
|
||||
|
||||
t.Run("network failure", func(t *testing.T) {
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
|
||||
// Serve the form_post template with a redirect URI that will return a network error.
|
||||
responseParams := formpostRandomParams(t)
|
||||
formpostInitiate(t, page, formpostTemplateServer(t, callbackURL+"?fail=close", responseParams))
|
||||
formpostInitiate(t, browser, formpostTemplateServer(t, callbackURL+"?fail=close", responseParams))
|
||||
|
||||
// Now we handle the callback and assert that we got what we expected.
|
||||
// This will trigger the callback server to close the client connection abruptly because
|
||||
@@ -80,28 +82,30 @@ func TestFormPostHTML_Browser_Parallel(t *testing.T) {
|
||||
expectCallback(t, responseParams)
|
||||
|
||||
// This failure should cause the UI to enter the "manual" state.
|
||||
actualCode := formpostExpectManualState(t, page)
|
||||
actualCode := formpostExpectManualState(t, browser)
|
||||
require.Equal(t, responseParams.Get("code"), actualCode)
|
||||
})
|
||||
|
||||
t.Run("timeout", func(t *testing.T) {
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
|
||||
// Serve the form_post template with successful parameters.
|
||||
responseParams := formpostRandomParams(t)
|
||||
formpostInitiate(t, page, formpostTemplateServer(t, callbackURL, responseParams))
|
||||
formpostInitiate(t, browser, formpostTemplateServer(t, callbackURL, responseParams))
|
||||
|
||||
// Sleep for longer than the two second timeout.
|
||||
// During this sleep we are blocking the callback from returning.
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// Assert that the timeout fires and we see the manual instructions.
|
||||
actualCode := formpostExpectManualState(t, page)
|
||||
actualCode := formpostExpectManualState(t, browser)
|
||||
require.Equal(t, responseParams.Get("code"), actualCode)
|
||||
|
||||
// Now simulate the callback finally succeeding, in which case
|
||||
// the manual instructions should disappear and we should see the success
|
||||
// div instead.
|
||||
expectCallback(t, responseParams)
|
||||
formpostExpectSuccessState(t, page)
|
||||
formpostExpectSuccessState(t, browser)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -228,88 +232,66 @@ func formpostRandomParams(t *testing.T) url.Values {
|
||||
}
|
||||
}
|
||||
|
||||
// formpostExpectTitle asserts that the page has the expected title.
|
||||
func formpostExpectTitle(t *testing.T, page *agouti.Page, expected string) {
|
||||
// formpostExpectFavicon asserts that the page has the expected SVG/emoji favicon.
|
||||
func formpostExpectFavicon(t *testing.T, b *browsertest.Browser, expected string) {
|
||||
t.Helper()
|
||||
actual, err := page.Title()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
// formpostExpectTitle asserts that the page has the expected SVG/emoji favicon.
|
||||
func formpostExpectFavicon(t *testing.T, page *agouti.Page, expected string) {
|
||||
t.Helper()
|
||||
iconURL, err := page.First("#favicon").Attribute("href")
|
||||
require.NoError(t, err)
|
||||
iconURL := b.AttrValueOfFirstMatch(t, "#favicon", "href")
|
||||
require.True(t, strings.HasPrefix(iconURL, "data:image/svg+xml,<svg"))
|
||||
|
||||
// For some reason chromedriver on Linux returns this attribute urlencoded, but on macOS it contains the
|
||||
// original emoji bytes (unescaped). To check correctly in both cases we allow either version here.
|
||||
expectedEscaped := url.QueryEscape(expected)
|
||||
require.Truef(t,
|
||||
strings.Contains(iconURL, expected) || strings.Contains(iconURL, expectedEscaped),
|
||||
"expected %q to contain %q or %q", iconURL, expected, expectedEscaped,
|
||||
)
|
||||
require.Contains(t, iconURL, expected)
|
||||
}
|
||||
|
||||
// formpostInitiate navigates to the template server endpoint and expects the
|
||||
// loading animation to be shown.
|
||||
func formpostInitiate(t *testing.T, page *agouti.Page, url string) {
|
||||
func formpostInitiate(t *testing.T, b *browsertest.Browser, url string) {
|
||||
t.Helper()
|
||||
require.NoError(t, page.Reset())
|
||||
t.Logf("navigating to mock form_post template URL %s...", url)
|
||||
require.NoError(t, page.Navigate(url))
|
||||
b.Navigate(t, url)
|
||||
|
||||
t.Logf("expecting to see loading animation...")
|
||||
browsertest.WaitForVisibleElements(t, page, "#loading")
|
||||
formpostExpectTitle(t, page, "Logging in...")
|
||||
formpostExpectFavicon(t, page, "⏳")
|
||||
b.WaitForVisibleElements(t, "div#loading")
|
||||
require.Equal(t, "Logging in...", b.Title(t))
|
||||
formpostExpectFavicon(t, b, "⏳")
|
||||
}
|
||||
|
||||
// formpostExpectSuccessState asserts that the page is in the "success" state.
|
||||
func formpostExpectSuccessState(t *testing.T, page *agouti.Page) {
|
||||
func formpostExpectSuccessState(t *testing.T, b *browsertest.Browser) {
|
||||
t.Helper()
|
||||
t.Logf("expecting to see success message become visible...")
|
||||
browsertest.WaitForVisibleElements(t, page, "#success")
|
||||
successDivText, err := page.First("#success").Text()
|
||||
require.NoError(t, err)
|
||||
b.WaitForVisibleElements(t, "div#success")
|
||||
successDivText := b.TextOfFirstMatch(t, "div#success")
|
||||
require.Contains(t, successDivText, "Login succeeded")
|
||||
require.Contains(t, successDivText, "You have successfully logged in. You may now close this tab.")
|
||||
formpostExpectTitle(t, page, "Login succeeded")
|
||||
formpostExpectFavicon(t, page, "✅")
|
||||
require.Equal(t, "Login succeeded", b.Title(t))
|
||||
formpostExpectFavicon(t, b, "✅")
|
||||
}
|
||||
|
||||
// formpostExpectManualState asserts that the page is in the "manual" state and returns the auth code.
|
||||
func formpostExpectManualState(t *testing.T, page *agouti.Page) string {
|
||||
func formpostExpectManualState(t *testing.T, b *browsertest.Browser) string {
|
||||
t.Helper()
|
||||
t.Logf("expecting to see manual message become visible...")
|
||||
browsertest.WaitForVisibleElements(t, page, "#manual")
|
||||
manualDivText, err := page.First("#manual").Text()
|
||||
require.NoError(t, err)
|
||||
b.WaitForVisibleElements(t, "div#manual")
|
||||
manualDivText := b.TextOfFirstMatch(t, "div#manual")
|
||||
require.Contains(t, manualDivText, "Finish your login")
|
||||
require.Contains(t, manualDivText, "To finish logging in, paste this authorization code into your command-line session:")
|
||||
formpostExpectTitle(t, page, "Finish your login")
|
||||
formpostExpectFavicon(t, page, "⌛")
|
||||
require.Equal(t, "Finish your login", b.Title(t))
|
||||
formpostExpectFavicon(t, b, "⌛")
|
||||
|
||||
// Click the copy button and expect that the code is copied to the clipboard. Unfortunately,
|
||||
// headless Chrome does not have a real clipboard we can check, so we rely on checking a
|
||||
// console.log() statement that happens at the same time.
|
||||
t.Logf("clicking the 'copy' button and expecting the clipboard event to fire...")
|
||||
require.NoError(t, page.First("#manual-copy-button").Click())
|
||||
b.ClickFirstMatch(t, "#manual-copy-button")
|
||||
|
||||
var authCode string
|
||||
consoleLogPattern := regexp.MustCompile(`code (.+) to clipboard`)
|
||||
testlib.RequireEventually(t, func(requireEventually *require.Assertions) {
|
||||
logs, err := page.ReadNewLogs("browser")
|
||||
requireEventually.NoError(err)
|
||||
|
||||
for _, log := range logs {
|
||||
if match := consoleLogPattern.FindStringSubmatch(log.Message); match != nil {
|
||||
authCode = match[1]
|
||||
return
|
||||
}
|
||||
matchingText, found := b.FindConsoleEventWithTextMatching("info", consoleLogPattern)
|
||||
requireEventually.True(found)
|
||||
if captureMatches := consoleLogPattern.FindStringSubmatch(matchingText); captureMatches != nil {
|
||||
authCode = captureMatches[1]
|
||||
return
|
||||
}
|
||||
requireEventually.FailNow("expected console log was not found")
|
||||
}, 3*time.Second, 100*time.Millisecond)
|
||||
}, 10*time.Second, 100*time.Millisecond)
|
||||
return authCode
|
||||
}
|
||||
|
||||
@@ -2429,15 +2429,15 @@ func requestAuthorizationAndExpectImmediateRedirectToCallback(t *testing.T, _, d
|
||||
t.Helper()
|
||||
|
||||
// Open the web browser and navigate to the downstream authorize URL.
|
||||
page := browsertest.Open(t)
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
t.Logf("opening browser to downstream authorize URL %s", testlib.MaskTokens(downstreamAuthorizeURL))
|
||||
require.NoError(t, page.Navigate(downstreamAuthorizeURL))
|
||||
browser.Navigate(t, downstreamAuthorizeURL)
|
||||
|
||||
// Expect that it immediately redirects back to the callback, which is what happens for certain types of errors
|
||||
// where it is not worth redirecting to the login UI page.
|
||||
t.Logf("waiting for redirect to callback")
|
||||
callbackURLPattern := regexp.MustCompile(`\A` + regexp.QuoteMeta(downstreamCallbackURL) + `\?.+\z`)
|
||||
browsertest.WaitForURL(t, page, callbackURLPattern)
|
||||
browser.WaitForURL(t, callbackURLPattern)
|
||||
}
|
||||
|
||||
func requestAuthorizationUsingBrowserAuthcodeFlowOIDC(t *testing.T, _, downstreamAuthorizeURL, downstreamCallbackURL, _, _ string, httpClient *http.Client) {
|
||||
@@ -2451,17 +2451,17 @@ func requestAuthorizationUsingBrowserAuthcodeFlowOIDC(t *testing.T, _, downstrea
|
||||
makeAuthorizationRequestAndRequireSecurityHeaders(ctx, t, downstreamAuthorizeURL, httpClient)
|
||||
|
||||
// Open the web browser and navigate to the downstream authorize URL.
|
||||
page := browsertest.Open(t)
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
t.Logf("opening browser to downstream authorize URL %s", testlib.MaskTokens(downstreamAuthorizeURL))
|
||||
require.NoError(t, page.Navigate(downstreamAuthorizeURL))
|
||||
browser.Navigate(t, downstreamAuthorizeURL)
|
||||
|
||||
// Expect to be redirected to the upstream provider and log in.
|
||||
browsertest.LoginToUpstreamOIDC(t, page, env.SupervisorUpstreamOIDC)
|
||||
browsertest.LoginToUpstreamOIDC(t, browser, env.SupervisorUpstreamOIDC)
|
||||
|
||||
// Wait for the login to happen and us be redirected back to a localhost callback.
|
||||
t.Logf("waiting for redirect to callback")
|
||||
callbackURLPattern := regexp.MustCompile(`\A` + regexp.QuoteMeta(downstreamCallbackURL) + `\?.+\z`)
|
||||
browsertest.WaitForURL(t, page, callbackURLPattern)
|
||||
browser.WaitForURL(t, callbackURLPattern)
|
||||
}
|
||||
|
||||
func requestAuthorizationUsingBrowserAuthcodeFlowLDAP(t *testing.T, downstreamIssuer, downstreamAuthorizeURL, downstreamCallbackURL, username, password string, httpClient *http.Client) {
|
||||
@@ -2474,51 +2474,51 @@ func requestAuthorizationUsingBrowserAuthcodeFlowLDAP(t *testing.T, downstreamIs
|
||||
makeAuthorizationRequestAndRequireSecurityHeaders(ctx, t, downstreamAuthorizeURL, httpClient)
|
||||
|
||||
// Open the web browser and navigate to the downstream authorize URL.
|
||||
page := browsertest.Open(t)
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
t.Logf("opening browser to downstream authorize URL %s", testlib.MaskTokens(downstreamAuthorizeURL))
|
||||
require.NoError(t, page.Navigate(downstreamAuthorizeURL))
|
||||
browser.Navigate(t, downstreamAuthorizeURL)
|
||||
|
||||
// Expect to be redirected to the upstream provider and log in.
|
||||
browsertest.LoginToUpstreamLDAP(t, page, downstreamIssuer, username, password)
|
||||
browsertest.LoginToUpstreamLDAP(t, browser, downstreamIssuer, username, password)
|
||||
|
||||
// Wait for the login to happen and us be redirected back to a localhost callback.
|
||||
t.Logf("waiting for redirect to callback")
|
||||
callbackURLPattern := regexp.MustCompile(`\A` + regexp.QuoteMeta(downstreamCallbackURL) + `\?.+\z`)
|
||||
browsertest.WaitForURL(t, page, callbackURLPattern)
|
||||
browser.WaitForURL(t, callbackURLPattern)
|
||||
}
|
||||
|
||||
func requestAuthorizationUsingBrowserAuthcodeFlowLDAPWithBadCredentials(t *testing.T, downstreamIssuer, downstreamAuthorizeURL, _, username, password string, _ *http.Client) {
|
||||
t.Helper()
|
||||
|
||||
// Open the web browser and navigate to the downstream authorize URL.
|
||||
page := browsertest.Open(t)
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
t.Logf("opening browser to downstream authorize URL %s", testlib.MaskTokens(downstreamAuthorizeURL))
|
||||
require.NoError(t, page.Navigate(downstreamAuthorizeURL))
|
||||
browser.Navigate(t, downstreamAuthorizeURL)
|
||||
|
||||
// This functions assumes that it has been passed either a bad username or a bad password, and submits the
|
||||
// provided credentials. Expect to be redirected to the upstream provider and attempt to log in.
|
||||
browsertest.LoginToUpstreamLDAP(t, page, downstreamIssuer, username, password)
|
||||
browsertest.LoginToUpstreamLDAP(t, browser, downstreamIssuer, username, password)
|
||||
|
||||
// After failing login expect to land back on the login page again with an error message.
|
||||
browsertest.WaitForUpstreamLDAPLoginPageWithError(t, page, downstreamIssuer)
|
||||
browsertest.WaitForUpstreamLDAPLoginPageWithError(t, browser, downstreamIssuer)
|
||||
}
|
||||
|
||||
func requestAuthorizationUsingBrowserAuthcodeFlowLDAPWithBadCredentialsAndThenGoodCredentials(t *testing.T, downstreamIssuer, downstreamAuthorizeURL, _, username, password string, _ *http.Client) {
|
||||
t.Helper()
|
||||
|
||||
// Open the web browser and navigate to the downstream authorize URL.
|
||||
page := browsertest.Open(t)
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
t.Logf("opening browser to downstream authorize URL %s", testlib.MaskTokens(downstreamAuthorizeURL))
|
||||
require.NoError(t, page.Navigate(downstreamAuthorizeURL))
|
||||
browser.Navigate(t, downstreamAuthorizeURL)
|
||||
|
||||
// Expect to be redirected to the upstream provider and attempt to log in.
|
||||
browsertest.LoginToUpstreamLDAP(t, page, downstreamIssuer, username, "this is the wrong password!")
|
||||
browsertest.LoginToUpstreamLDAP(t, browser, downstreamIssuer, username, "this is the wrong password!")
|
||||
|
||||
// After failing login expect to land back on the login page again with an error message.
|
||||
browsertest.WaitForUpstreamLDAPLoginPageWithError(t, page, downstreamIssuer)
|
||||
browsertest.WaitForUpstreamLDAPLoginPageWithError(t, browser, downstreamIssuer)
|
||||
|
||||
// Already at the login page, so this time can directly submit it using the provided username and password.
|
||||
browsertest.SubmitUpstreamLDAPLoginForm(t, page, username, password)
|
||||
browsertest.SubmitUpstreamLDAPLoginForm(t, browser, username, password)
|
||||
}
|
||||
|
||||
func makeAuthorizationRequestAndRequireSecurityHeaders(ctx context.Context, t *testing.T, downstreamAuthorizeURL string, httpClient *http.Client) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package integration
|
||||
|
||||
@@ -355,7 +355,7 @@ func TestSupervisorWarnings_Browser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Start a fresh browser driver because we don't want to share cookies between the various tests in this file.
|
||||
page := browsertest.Open(t)
|
||||
browser := browsertest.OpenBrowser(t)
|
||||
|
||||
expectedUsername := env.SupervisorUpstreamOIDC.Username
|
||||
|
||||
@@ -436,17 +436,17 @@ func TestSupervisorWarnings_Browser(t *testing.T) {
|
||||
require.NotEmptyf(t, loginURL, "didn't find login URL in output: %s", output)
|
||||
|
||||
t.Logf("navigating to login page")
|
||||
require.NoError(t, page.Navigate(loginURL))
|
||||
browser.Navigate(t, loginURL)
|
||||
|
||||
// Expect to be redirected to the upstream provider and log in.
|
||||
browsertest.LoginToUpstreamOIDC(t, page, env.SupervisorUpstreamOIDC)
|
||||
browsertest.LoginToUpstreamOIDC(t, browser, env.SupervisorUpstreamOIDC)
|
||||
|
||||
// Expect to be redirected to the downstream callback which is serving the form_post HTML.
|
||||
t.Logf("waiting for response page %s", downstream.Spec.Issuer)
|
||||
browsertest.WaitForURL(t, page, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer)))
|
||||
browser.WaitForURL(t, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer)))
|
||||
|
||||
// The response page should have failed to automatically post, and should now be showing the manual instructions.
|
||||
authCode := formpostExpectManualState(t, page)
|
||||
authCode := formpostExpectManualState(t, browser)
|
||||
|
||||
// Enter the auth code in the waiting prompt, followed by a newline.
|
||||
t.Logf("'manually' pasting authorization code %q to waiting prompt", authCode)
|
||||
|
||||
Reference in New Issue
Block a user