From 5cbcae40a6dc51193c198a90b55bdfb443757e5d Mon Sep 17 00:00:00 2001 From: ulisesL Date: Thu, 14 Jul 2022 10:47:52 -0700 Subject: [PATCH] Improve 3LO loopback tests (#137) --- integration/cli_test.go | 291 +++++++++++++++--- .../fake-client-secrets-3lo-loopback.json | 2 +- ...nt-secrets-expired-token-3lo-loopback.json | 16 + .../fake-client-secrets-expired-token.json | 2 +- integration/fixtures/fake-client-secrets.json | 2 +- integration/golden/curl-3lo-loopback.golden | 2 +- .../fetch-3lo-loopback-refresh-token.golden | 2 +- .../golden/fetch-3lo-loopback-userinfo.golden | 2 +- integration/golden/fetch-3lo-loopback.golden | 2 +- integration/golden/header-3lo-loopback.golden | 2 +- util/cache.go | 2 +- util/loopback.go | 16 +- 12 files changed, 281 insertions(+), 60 deletions(-) create mode 100644 integration/fixtures/fake-client-secrets-expired-token-3lo-loopback.json diff --git a/integration/cli_test.go b/integration/cli_test.go index 7e73b4aa..fcc3e4dd 100644 --- a/integration/cli_test.go +++ b/integration/cli_test.go @@ -23,12 +23,16 @@ import ( "net/http" "os" "os/exec" + "path" "path/filepath" "reflect" "regexp" "runtime" "strings" "testing" + "time" + + "github.com/google/oauth2l/util" ) // Use this flag to update golden files with test outputs from current run. @@ -92,6 +96,18 @@ func (tf *testFile) load() string { return string(content) } +// saveAs saves the currect instance into another file in the same directory. +// The current instance is not substituted by the newly create one. +// +// input filename: is the new filename. +func (tf *testFile) saveAs(filename string) { + content := tf.load() + temp := tf.name + tf.name = filename + tf.write(content) + tf.name = temp +} + type testCase struct { name string args []string @@ -112,17 +128,40 @@ func runTestScenariosWithInput(t *testing.T, tests []testCase, input *os.File) { // Used for processing test output before comparing to golden files. type processOutput func(string) string -// Runs test cases where stdin input is needed and output needs to be processed before comparing to golden files. -func runTestScenariosWithInputAndProcessedOutput(t *testing.T, tests []testCase, input *os.File, processOutput processOutput) { +// Used for additional logic before executing oauth2l's command. +type preCommandLogic func(tc *testCase) error + +// Used for additional logic after executing oauth2l's command. +type postCommandLogic func() + +// Runs tests where extra logic needs to be added before/after the command execution. +// +// input processOutput: processes test output before comparing to golden files +// input preCmdLogic: additional logic excuted once per test case before command excution. It allows for test argument manipulation. +// input postCmdLogic: additional logic excuted once per test case after command excution. It allows for cleanup. +func runTestScenariosWithAdvancedLogic(t *testing.T, tests []testCase, input *os.File, processOutput processOutput, + preCmdLogic preCommandLogic, postCmdLogic postCommandLogic) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - cmd := exec.Command(binaryPath, tc.args...) + // Processing logic before exec. Command + if preCmdLogic != nil { + if err := preCmdLogic(&tc); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + } + cmd := exec.Command(binaryPath, tc.args...) if input != nil { cmd.Stdin = input } + // exec.Command and gathering output output, err := cmd.CombinedOutput() + + // Processing logic after exec. Command + if postCmdLogic != nil { + postCmdLogic() + } if (err != nil) != tc.wantErr { t.Fatalf("%s\nexpected (err != nil) to be %v, but got %v. err: %v", output, tc.wantErr, err != nil, err) } @@ -145,6 +184,21 @@ func runTestScenariosWithInputAndProcessedOutput(t *testing.T, tests []testCase, } } +// Runs test cases where stdin input is needed and output needs to be processed before comparing to golden files. +func runTestScenariosWithInputAndProcessedOutput(t *testing.T, tests []testCase, input *os.File, processOutput processOutput) { + runTestScenariosWithAdvancedLogic(t, tests, input, processOutput, nil, nil) +} + +// Helper for removing the randomly generated redirect uri's port from comparison. +func removeRedirectUriPort(s string) string { + re := regexp.MustCompile("redirect_uri=.*http%3A%2F%2Flocalhost%3A\\d+") + match := re.FindString(s) + if match != "" { + return strings.Replace(s, match, "redirect_uri=http%3A%2F%2Flocalhost", 1) + } + return s +} + // Helper for removing the randomly generated code_challenge string from comparison. func removeCodeChallenge(s string) string { re := regexp.MustCompile("code_challenge=.*code_challenge_method") @@ -306,17 +360,24 @@ func Test3LOFlow(t *testing.T) { runTestScenariosWithInputAndProcessedOutput(t, tests, newFixture(t, "fake-verification-code.fixture").asFile(), process3LOOutput) } -// TODO: Enhance tests so that the entire loopback flow can be tested -// TODO: Once enhanced, uncomment and fix cache tests in this flow // TODO: Remove Test3LOFlow once the 3LO flow is deprecated -// Test OAuth 3LO loopback flow with fake client secrets. Stops waiting for consent page interaction to advance the flow. +// Test OAuth 3LO loopback flow with fake client secrets. It does not wait for consent page interaction. +// Instead a post request with the code and state is sent to the loopback server to advance the flow. func Test3LOLoopbackFlow(t *testing.T) { + + const ( + // NOTE: Update all consent page settings accordingly if one is changed. + CONSENT_PAGE_TIMEOUT = "30" + CONSENT_PAGE_TIMEOUT_UNITS = "seconds" + CONSENT_PAGE_DURATION = time.Duration(30 * time.Second) + ) + tests := []testCase{ { "fetch; 3lo loopback", []string{"fetch", "--scope", "pubsub", "--credentials", "integration/fixtures/fake-client-secrets-3lo-loopback.json", "--cache", "", "--disableAutoOpenConsentPage", - "--consentPageInteractionTimeout", "1", "--consentPageInteractionTimeoutUnits", "seconds"}, + "--consentPageInteractionTimeout", CONSENT_PAGE_TIMEOUT, "--consentPageInteractionTimeoutUnits", CONSENT_PAGE_TIMEOUT_UNITS}, "fetch-3lo-loopback.golden", false, }, @@ -324,14 +385,14 @@ func Test3LOLoopbackFlow(t *testing.T) { "fetch; 3lo loopback; old interface", []string{"fetch", "--json", "integration/fixtures/fake-client-secrets-3lo-loopback.json", "--cache", "", "pubsub", "--disableAutoOpenConsentPage", - "--consentPageInteractionTimeout", "1", "--consentPageInteractionTimeoutUnits", "seconds"}, + "--consentPageInteractionTimeout", CONSENT_PAGE_TIMEOUT, "--consentPageInteractionTimeoutUnits", CONSENT_PAGE_TIMEOUT_UNITS}, "fetch-3lo-loopback.golden", false, }, { "fetch; 3lo loopback; userinfo scopes", []string{"fetch", "--scope", "userinfo.profile,userinfo.email", "--credentials", "integration/fixtures/fake-client-secrets-3lo-loopback.json", "--cache", "", - "--consentPageInteractionTimeout", "1", "--consentPageInteractionTimeoutUnits", "seconds", + "--consentPageInteractionTimeout", CONSENT_PAGE_TIMEOUT, "--consentPageInteractionTimeoutUnits", CONSENT_PAGE_TIMEOUT_UNITS, "--disableAutoOpenConsentPage"}, "fetch-3lo-loopback-userinfo.golden", false, @@ -339,7 +400,7 @@ func Test3LOLoopbackFlow(t *testing.T) { { "header; 3lo loopback", []string{"header", "--scope", "pubsub", "--credentials", "integration/fixtures/fake-client-secrets-3lo-loopback.json", "--cache", "", - "--consentPageInteractionTimeout", "1", "--consentPageInteractionTimeoutUnits", "seconds", + "--consentPageInteractionTimeout", CONSENT_PAGE_TIMEOUT, "--consentPageInteractionTimeoutUnits", CONSENT_PAGE_TIMEOUT_UNITS, "--disableAutoOpenConsentPage"}, "header-3lo-loopback.golden", false, @@ -347,7 +408,7 @@ func Test3LOLoopbackFlow(t *testing.T) { { "fetch; 3lo loopback; refresh token output format", []string{"fetch", "--output_format", "refresh_token", "--scope", "pubsub", "--credentials", "integration/fixtures/fake-client-secrets-3lo-loopback.json", "--cache", "", - "--consentPageInteractionTimeout", "1", "--consentPageInteractionTimeoutUnits", "seconds", + "--consentPageInteractionTimeout", CONSENT_PAGE_TIMEOUT, "--consentPageInteractionTimeoutUnits", CONSENT_PAGE_TIMEOUT_UNITS, "--disableAutoOpenConsentPage"}, "fetch-3lo-loopback-refresh-token.golden", false, @@ -355,49 +416,147 @@ func Test3LOLoopbackFlow(t *testing.T) { { "curl; 3lo loopback", []string{"curl", "--scope", "pubsub", "--credentials", "integration/fixtures/fake-client-secrets-3lo-loopback.json", "--url", "http://localhost:8080/curl", - "--consentPageInteractionTimeout", "1", "--consentPageInteractionTimeoutUnits", "seconds", + "--consentPageInteractionTimeout", CONSENT_PAGE_TIMEOUT, "--consentPageInteractionTimeoutUnits", CONSENT_PAGE_TIMEOUT_UNITS, "--disableAutoOpenConsentPage"}, "curl-3lo-loopback.golden", false, }, - /* - { - "fetch; 3lo loopback cached", - []string{"fetch", "--scope", "pubsub", "--credentials", "integration/fixtures/fake-client-secrets-3lo-loopback.json", "--consentPageInteractionTimeout", "1", "--consentPageInteractionTimeoutUnits", "seconds"}, - "fetch-3lo-cached.golden", - false, - }, - { - "fetch; 3lo loopback insert expired token into cache", - []string{"fetch", "--scope", "pubsub", "--credentials", "integration/fixtures/fake-client-secrets-expired-token-3lo-loopback.json", - "--consentPageInteractionTimeout", "1", "--consentPageInteractionTimeoutUnits", "seconds"}, - "fetch-3lo.golden", - false, - }, - { - "fetch; 3lo loopback cached; token expired", - []string{"fetch", "--scope", "pubsub", "--credentials", "integration/fixtures/fake-client-secrets-expired-token-3lo-loopback.json", - "--consentPageInteractionTimeout", "1", "--consentPageInteractionTimeoutUnits", "seconds"}, - "fetch-3lo.golden", - false, - }, - { - "fetch; 3lo loopback cached; refresh expired token", - []string{"fetch", "--scope", "pubsub", "--credentials", "integration/fixtures/fake-client-secrets-expired-token-3lo-loopback.json", "--refresh", - "--consentPageInteractionTimeout", "1", "--consentPageInteractionTimeoutUnits", "seconds"}, - "fetch-3lo-cached.golden", - false, - },*/ + { + "fetch; 3lo loopback cached", + []string{"fetch", "--scope", "pubsub", "--credentials", "integration/fixtures/fake-client-secrets-3lo-loopback.json", + "--disableAutoOpenConsentPage", + "--consentPageInteractionTimeout", CONSENT_PAGE_TIMEOUT, "--consentPageInteractionTimeoutUnits", CONSENT_PAGE_TIMEOUT_UNITS}, + "fetch-3lo-cached.golden", + false, + }, + { + "fetch; 3lo loopback insert expired token into cache", + []string{"fetch", "--scope", "pubsub", "--credentials", "integration/fixtures/fake-client-secrets-expired-token-3lo-loopback.json", + "--disableAutoOpenConsentPage", + "--consentPageInteractionTimeout", CONSENT_PAGE_TIMEOUT, "--consentPageInteractionTimeoutUnits", CONSENT_PAGE_TIMEOUT_UNITS}, + "fetch-3lo-loopback.golden", + false, + }, + { + "fetch; 3lo loopback cached; token expired", + []string{"fetch", "--scope", "pubsub", "--credentials", "integration/fixtures/fake-client-secrets-expired-token-3lo-loopback.json", + "--disableAutoOpenConsentPage", + "--consentPageInteractionTimeout", CONSENT_PAGE_TIMEOUT, "--consentPageInteractionTimeoutUnits", CONSENT_PAGE_TIMEOUT_UNITS}, + "fetch-3lo-loopback.golden", + false, + }, + { + "fetch; 3lo loopback cached; refresh expired token", + []string{"fetch", "--scope", "pubsub", "--credentials", "integration/fixtures/fake-client-secrets-expired-token-3lo-loopback.json", "--refresh", + "--disableAutoOpenConsentPage", + "--consentPageInteractionTimeout", CONSENT_PAGE_TIMEOUT, "--consentPageInteractionTimeoutUnits", CONSENT_PAGE_TIMEOUT_UNITS}, + "fetch-3lo-cached.golden", + false, + }, } - process3LOOutput := func(output string) string { - re := regexp.MustCompile("redirect_uri=http%3A%2F%2Flocalhost%3A\\d+") - match := re.FindString(output) - output = strings.Replace(output, match, "redirect_uri=http%3A%2F%2Flocalhost", 1) - return removeCodeChallenge(output) + type LoopbackLogicState struct { + quitRetry bool + cred *testFile + redirectUri string + } + loopbackLogic := func() (func(tc *testCase) error, func()) { + const ( + CODE_AND_STATE = "/?state=state&code=4/gwEhAq4N7tdTj4ZStstQgaDAUpcoceoFSEPmSsoWEKVZoYSn6URLVEw" + QUOTED_LOCALHOST = "\"http://localhost\"" + TEMP_CRED_FILE = "temp-cred-file.json" + ) + + // Looking for available port. + // The port is passed to the the advance logic functions. + l, addr, err := util.GetListener("http://localhost") + if err != nil { + t.Fatalf("Error when getting listener: %v", err) + } + (*l).Close() + + var ll *LoopbackLogicState = &LoopbackLogicState{ + quitRetry: false, + cred: nil, + redirectUri: addr, + } + + preLogic := func(tc *testCase) error { + // Partially resetting state - keeping redirect uri. + (*ll).quitRetry = false + (*ll).cred = nil + + // searching for credentials filename. + f := getCredentialsFileName(tc) + if f == "" { + return fmt.Errorf("Credentials file is missing. Please add to test arguments.") + } + changeCredentialsFileName(tc, TEMP_CRED_FILE) + + // Loading credentials file + origFile := newFixture(t, path.Base(f)) + origFile.saveAs(TEMP_CRED_FILE) + + // Loading credentials file temp copy + (*ll).cred = newFixture(t, TEMP_CRED_FILE) + fileContent := (*ll).cred.load() + + // Modifiying credentials file temp copy: redirect uri + re := regexp.MustCompile(QUOTED_LOCALHOST) + match := re.FindString(fileContent) + newContent := strings.Replace(fileContent, match, "\""+(*ll).redirectUri+"\"", 1) + (*ll).cred.write(newContent) + + // Start loopback logic. + go func() { + timer := time.AfterFunc(CONSENT_PAGE_DURATION, func() { + // Force ending the retry loop, so the retry logic does not loop + // forever. postLogic may trigger an end retry logic in case the + // exec. command finishes before the CONSET_PAGE_DURATION timeout. + (*ll).quitRetry = true + }) + defer timer.Stop() + + code_state_endpoint := (*ll).redirectUri + CODE_AND_STATE + for (*ll).quitRetry != true { + req, err := http.NewRequest("POST", code_state_endpoint, nil) + if err == nil { + // Sending code and state message to the localhost server handling + // the authentication code - see loopback.go for more detials. + res, err := http.DefaultClient.Do(req) + if err == nil { + res.Body.Close() + // Ending the retry loop + (*ll).quitRetry = true + // Bypass sleep + return + } + } + // If unable to reach code_state_endpoint wait a second. + time.Sleep(1 * time.Second) + } + }() + return nil + } + + postLogic := func() { + // End loopback logic if it is still retrying: + // In the event where exec Command exits prematurely, the loopback loop + // should not try to POST the code and state - It would only waste resources. + (*ll).quitRetry = true + // Removing temp credentials file. + os.Remove((*ll).cred.path()) + return + } + + return preLogic, postLogic } - runTestScenariosWithInputAndProcessedOutput(t, tests, nil, process3LOOutput) + pre, post := loopbackLogic() + process3LOOutput := func(output string) string { + return removeCodeChallenge(removeRedirectUriPort(output)) + } + runTestScenariosWithAdvancedLogic(t, tests, nil, process3LOOutput, pre, post) } // Test OAuth 2LO Flow with fake service account. @@ -540,6 +699,46 @@ func TestServiceAccountImpersonationFlow(t *testing.T) { runTestScenariosWithInputAndProcessedOutput(t, tests, nil, processOutput) } +// getCredentialsFileName finds the credentials filename provided in the testCase arguments. +// If no filename is found, an empty string is returned. +// +// Note: the "--credentials" or "--json" options are used to find the credentials file. +// +// input tc: is the test case. +func getCredentialsFileName(tc *testCase) string { + var a string + var i int + for i, a = range tc.args { + if a == "--credentials" || a == "--json" { + break + } + } + if i >= len(tc.args)-1 { + return "" + } + return path.Base(tc.args[i+1]) +} + +// changeCredentialsFileName replaces the credentials filename in the test arguments. +// If the credentials file is not found, no changes are made. +// +// input tc: is the test case. +// input filename: is the new filename. +func changeCredentialsFileName(tc *testCase, filename string) { + var a string + var i int + for i, a = range tc.args { + if a == "--credentials" || a == "--json" { + break + } + } + if i >= len(tc.args)-1 { + return + } + dir := path.Dir(tc.args[i+1]) + tc.args[i+1] = dir + "/" + filename +} + func readFile(path string) string { content, err := ioutil.ReadFile(path) if err != nil { diff --git a/integration/fixtures/fake-client-secrets-3lo-loopback.json b/integration/fixtures/fake-client-secrets-3lo-loopback.json index 7b103b7b..51c32c24 100644 --- a/integration/fixtures/fake-client-secrets-3lo-loopback.json +++ b/integration/fixtures/fake-client-secrets-3lo-loopback.json @@ -2,7 +2,7 @@ "installed": { "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "client_email": "", + "client_email": "loopback-test@gmail.com", "client_id": "144169.apps.googleusercontent.com", "project_id":"awesomeproject", "client_secret": "awesomesecret", diff --git a/integration/fixtures/fake-client-secrets-expired-token-3lo-loopback.json b/integration/fixtures/fake-client-secrets-expired-token-3lo-loopback.json new file mode 100644 index 00000000..7fd40e24 --- /dev/null +++ b/integration/fixtures/fake-client-secrets-expired-token-3lo-loopback.json @@ -0,0 +1,16 @@ +{ + "installed": { + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "client_email": "loopback-test@gmail.com", + "client_id": "144169.apps.googleusercontent.com", + "project_id":"awesomeproject", + "client_secret": "awesomesecret", + "client_x509_cert_url": "", + "redirect_uris": [ + "http://localhost", + "urn:ietf:wg:oauth:2.0:oob" + ], + "token_uri": "http://localhost:8080/expiredtoken" + } +} diff --git a/integration/fixtures/fake-client-secrets-expired-token.json b/integration/fixtures/fake-client-secrets-expired-token.json index d9e2beab..38d5a407 100644 --- a/integration/fixtures/fake-client-secrets-expired-token.json +++ b/integration/fixtures/fake-client-secrets-expired-token.json @@ -2,7 +2,7 @@ "installed": { "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "client_email": "", + "client_email": "test@gmail.com", "client_id": "144169.apps.googleusercontent.com", "project_id":"awesomeproject", "client_secret": "awesomesecret", diff --git a/integration/fixtures/fake-client-secrets.json b/integration/fixtures/fake-client-secrets.json index 27f95894..e701bb88 100644 --- a/integration/fixtures/fake-client-secrets.json +++ b/integration/fixtures/fake-client-secrets.json @@ -2,7 +2,7 @@ "installed": { "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "client_email": "", + "client_email": "test@gmail.com", "client_id": "144169.apps.googleusercontent.com", "project_id":"awesomeproject", "client_secret": "awesomesecret", diff --git a/integration/golden/curl-3lo-loopback.golden b/integration/golden/curl-3lo-loopback.golden index 26bd2b39..6319781e 100644 --- a/integration/golden/curl-3lo-loopback.golden +++ b/integration/golden/curl-3lo-loopback.golden @@ -1,4 +1,4 @@ Go to the following link in your browser: https://accounts.google.com/o/oauth2/auth?client_id=144169.apps.googleusercontent.com&code_challenge_method=S256&redirect_uri=http%3A%2F%2Flocalhost&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fpubsub&state=state -Authorization code not yet set. +{} \ No newline at end of file diff --git a/integration/golden/fetch-3lo-loopback-refresh-token.golden b/integration/golden/fetch-3lo-loopback-refresh-token.golden index 26bd2b39..6f7ce882 100644 --- a/integration/golden/fetch-3lo-loopback-refresh-token.golden +++ b/integration/golden/fetch-3lo-loopback-refresh-token.golden @@ -1,4 +1,4 @@ Go to the following link in your browser: https://accounts.google.com/o/oauth2/auth?client_id=144169.apps.googleusercontent.com&code_challenge_method=S256&redirect_uri=http%3A%2F%2Flocalhost&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fpubsub&state=state -Authorization code not yet set. +{"client_id":"144169.apps.googleusercontent.com","client_secret":"awesomesecret","token_uri":"http://localhost:8080/token","auth_uri":"https://accounts.google.com/o/oauth2/auth","refresh_token":"1/q8uQkblGs0Zzpe1LtpDtBLKsyf_NlEnPOxo1DcTR27U","type":"authorized_user"} diff --git a/integration/golden/fetch-3lo-loopback-userinfo.golden b/integration/golden/fetch-3lo-loopback-userinfo.golden index 0b10a6ec..7d9e9e93 100644 --- a/integration/golden/fetch-3lo-loopback-userinfo.golden +++ b/integration/golden/fetch-3lo-loopback-userinfo.golden @@ -1,4 +1,4 @@ Go to the following link in your browser: https://accounts.google.com/o/oauth2/auth?client_id=144169.apps.googleusercontent.com&code_challenge_method=S256&redirect_uri=http%3A%2F%2Flocalhost&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&state=state -Authorization code not yet set. +ya29.GltDB_y4Oz8lVB5diZu9YVMgHuXoSVBXx6jt7WU9n8IaXk63RejERFtx2LfrH-VL51CbaAxKsC8EoMZXg50h2QvOcUQ-YZTvFnKtIJpLj_Zj68M56_VagXpZkZd7 diff --git a/integration/golden/fetch-3lo-loopback.golden b/integration/golden/fetch-3lo-loopback.golden index 26bd2b39..eb227526 100644 --- a/integration/golden/fetch-3lo-loopback.golden +++ b/integration/golden/fetch-3lo-loopback.golden @@ -1,4 +1,4 @@ Go to the following link in your browser: https://accounts.google.com/o/oauth2/auth?client_id=144169.apps.googleusercontent.com&code_challenge_method=S256&redirect_uri=http%3A%2F%2Flocalhost&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fpubsub&state=state -Authorization code not yet set. +ya29.GltDB_y4Oz8lVB5diZu9YVMgHuXoSVBXx6jt7WU9n8IaXk63RejERFtx2LfrH-VL51CbaAxKsC8EoMZXg50h2QvOcUQ-YZTvFnKtIJpLj_Zj68M56_VagXpZkZd7 diff --git a/integration/golden/header-3lo-loopback.golden b/integration/golden/header-3lo-loopback.golden index 26bd2b39..ccd444cb 100644 --- a/integration/golden/header-3lo-loopback.golden +++ b/integration/golden/header-3lo-loopback.golden @@ -1,4 +1,4 @@ Go to the following link in your browser: https://accounts.google.com/o/oauth2/auth?client_id=144169.apps.googleusercontent.com&code_challenge_method=S256&redirect_uri=http%3A%2F%2Flocalhost&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fpubsub&state=state -Authorization code not yet set. +Authorization: Bearer ya29.GltDB_y4Oz8lVB5diZu9YVMgHuXoSVBXx6jt7WU9n8IaXk63RejERFtx2LfrH-VL51CbaAxKsC8EoMZXg50h2QvOcUQ-YZTvFnKtIJpLj_Zj68M56_VagXpZkZd7 diff --git a/util/cache.go b/util/cache.go index c8ecaf4c..fc499e1c 100644 --- a/util/cache.go +++ b/util/cache.go @@ -132,7 +132,7 @@ func createKey(settings *Settings) CacheKey { // Removing redirect_uri from credentials file. This allows for dynamic // localhost ports created during 3LO loopback. var credentialsJSON string = settings.CredentialsJSON - re := regexp.MustCompile("\"redirect_uris\":\\[(.*?)\\]") + re := regexp.MustCompile("\"redirect_uris\":([[:graph:]\\s]*?)\\]") match := re.FindString(credentialsJSON) credentialsJSON = strings.Replace(credentialsJSON, match, "\"redirect_uris\":[]", 1) diff --git a/util/loopback.go b/util/loopback.go index 0a4f8c1e..ce152361 100644 --- a/util/loopback.go +++ b/util/loopback.go @@ -28,6 +28,12 @@ import ( "time" ) +// Loopback server endpoints +const ( + SERVER_STATUS_ENDPOINT_URL = "/status/get" + SERVER_LOOPBACK_ENDPOINT_URL = "/" +) + type AuthorizationCodeRequestStatus int // Phases of the authorization code @@ -122,7 +128,7 @@ type AuthorizationCodeLocalhost struct { } func (lh *AuthorizationCodeLocalhost) ListenAndServe(address string) (serverAddress string, err error) { - listener, serverAddress, err := getListener(address) + listener, serverAddress, err := GetListener(address) if err != nil { return "", fmt.Errorf("Unable to Listen: %v", err) } @@ -132,8 +138,8 @@ func (lh *AuthorizationCodeLocalhost) ListenAndServe(address string) (serverAddr // Setup local host in given address mux := http.NewServeMux() lh.server = &http.Server{Addr: strings.Replace(lh.addr, "http://", "", 1), Handler: mux} - mux.HandleFunc("/", lh.redirectUriHandler) - mux.HandleFunc("/status/get", lh.statusGetHandler) + mux.HandleFunc(SERVER_LOOPBACK_ENDPOINT_URL, lh.redirectUriHandler) + mux.HandleFunc(SERVER_STATUS_ENDPOINT_URL, lh.statusGetHandler) go func() { // Start Listed and Serve @@ -293,7 +299,7 @@ func (lh *AuthorizationCodeLocalhost) statusGetHandler(w http.ResponseWriter, r return } -// getListener gets a listener on the port specified in the address. +// GetListener gets a listener on the port specified in the address. // If no port is specified in the address, an available port is assigned. // // Input address: represents a localhost address. Its format is http://localhost[:port] @@ -301,7 +307,7 @@ func (lh *AuthorizationCodeLocalhost) statusGetHandler(w http.ResponseWriter, r // Returns listener // Returns serverAddress: is the address of the listener. Its format is http://localhost[:port] // Returns err: if not nil an error occurred when creating the listener. -func getListener(address string) (listener *net.Listener, serverAddress string, err error) { +func GetListener(address string) (listener *net.Listener, serverAddress string, err error) { var l net.Listener = nil re := regexp.MustCompile("localhost:\\d+")