Replace agouti and chromedriver with chromedp across the whole project

This commit is contained in:
Ryan Richard
2023-08-01 09:04:21 -07:00
parent 2c27db0c85
commit 4512eeca9a
10 changed files with 389 additions and 279 deletions

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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)