diff --git a/internal/oonirun/v2.go b/internal/oonirun/v2.go index 3ed915a02..974be9790 100644 --- a/internal/oonirun/v2.go +++ b/internal/oonirun/v2.go @@ -7,6 +7,7 @@ package oonirun import ( "bufio" "context" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -305,13 +306,16 @@ func v2MeasureHTTPS(ctx context.Context, config *LinkConfig, URL string) error { func maybeGetAuthenticationTokenFromFile(path string) (string, error) { if path != "" { - return readFirstLineFromFile(path) + return readBearerTokenFromFile(path) } return "", nil } -// readFirstLineFromFile reads the first line of the passed text file and trims any non-printable characters. -func readFirstLineFromFile(filep string) (string, error) { +// readBearerTokenFromFile tries to extract a valid (base64) bearer token from +// the first line of the passed text file. +// If there is an error while reading from the file, the error will be returned. +// If we can read from the file but there's no valid token found, an empty string will be returned. +func readBearerTokenFromFile(filep string) (string, error) { f, err := fsx.OpenFile(filep) if err != nil { return "", err @@ -323,10 +327,29 @@ func readFirstLineFromFile(filep string) (string, error) { // Scan the first line if scanner.Scan() { line := scanner.Text() + + // trim any non printable characters (like control chars) trimmed := strings.TrimFunc(line, func(r rune) bool { return !unicode.IsPrint(r) }) - return trimmed, nil + + // tokenize by whitespace + tokens := strings.Fields(trimmed) + + // return empty string if tokens is empty + if len(tokens) <= 0 { + return "", nil + } + + // ignore all tokens after the first + token := tokens[0] + + // if this is not a valid base64 token, return empty string + if _, err := base64.StdEncoding.DecodeString(token); err != nil { + return "", nil + } + + return token, nil } // Check for any scanning error diff --git a/internal/oonirun/v2_test.go b/internal/oonirun/v2_test.go index f54ed827d..4cd58d014 100644 --- a/internal/oonirun/v2_test.go +++ b/internal/oonirun/v2_test.go @@ -267,7 +267,7 @@ func TestOONIRunV2LinkEmptyTestName(t *testing.T) { func TestOONIRunV2LinkWithAuthentication(t *testing.T) { t.Run("authentication raises error if no token is passed", func(t *testing.T) { - token := "secret-token" + token := "c2VjcmV0" bearerToken := "Bearer " + token // make a local server that returns a reasonable descriptor for the example experiment @@ -324,7 +324,7 @@ func TestOONIRunV2LinkWithAuthentication(t *testing.T) { }) t.Run("authentication does not fail the auth token is passed", func(t *testing.T) { - token := "secret-token" + token := "c2VjcmV0" bearerToken := "Bearer " + token // make a local server that returns a reasonable descriptor for the example experiment @@ -673,7 +673,25 @@ func Test_readFirstLineFromFile(t *testing.T) { defer f.Close() defer os.Remove(f.Name()) - line, err := readFirstLineFromFile(f.Name()) + line, err := readBearerTokenFromFile(f.Name()) + if line != "" { + t.Fatal("expected empty string") + } + if err != nil { + t.Fatal("expected err==nil") + } + }) + + t.Run("return empty string if first line is just whitespace", func(t *testing.T) { + f, err := os.CreateTemp(t.TempDir(), "auth-") + if err != nil { + t.Fatal(err) + } + f.Write([]byte(" \n")) + defer f.Close() + defer os.Remove(f.Name()) + + line, err := readBearerTokenFromFile(f.Name()) if line != "" { t.Fatal("expected empty string") } @@ -683,7 +701,7 @@ func Test_readFirstLineFromFile(t *testing.T) { }) t.Run("return error if file does not exist", func(t *testing.T) { - line, err := readFirstLineFromFile(filepath.Join(t.TempDir(), "non-existent")) + line, err := readBearerTokenFromFile(filepath.Join(t.TempDir(), "non-existent")) if line != "" { t.Fatal("expected empty string") } @@ -698,12 +716,12 @@ func Test_readFirstLineFromFile(t *testing.T) { t.Fatal(err) } - token := "asecret" + token := "c2VjcmV0" // b64("secret") f.Write([]byte(token)) defer f.Close() defer os.Remove(f.Name()) - line, err := readFirstLineFromFile(f.Name()) + line, err := readBearerTokenFromFile(f.Name()) if line != token { t.Fatalf("expected %s, got %s", token, line) } @@ -718,14 +736,36 @@ func Test_readFirstLineFromFile(t *testing.T) { t.Fatal(err) } - token := "asecret" + token := "c2VjcmV0" // b64("secret") f.Write([]byte(token)) f.Write([]byte("\n")) f.Write([]byte("something\nelse\nand\nsomething\nmore")) defer f.Close() defer os.Remove(f.Name()) - line, err := readFirstLineFromFile(f.Name()) + line, err := readBearerTokenFromFile(f.Name()) + if line != token { + t.Fatalf("expected %s, got %s", token, line) + } + if err != nil { + t.Fatal("expected err==nil") + } + }) + + t.Run("return only first token if >1 is found", func(t *testing.T) { + f, err := os.CreateTemp(t.TempDir(), "auth-") + if err != nil { + t.Fatal(err) + } + + token := "c2VjcmV0" // b64("secret") + f.Write([]byte(token)) + f.Write([]byte("\n")) + f.Write([]byte(" antani\n")) + defer f.Close() + defer os.Remove(f.Name()) + + line, err := readBearerTokenFromFile(f.Name()) if line != token { t.Fatalf("expected %s, got %s", token, line) } @@ -734,4 +774,28 @@ func Test_readFirstLineFromFile(t *testing.T) { } }) + t.Run("return empty string if not a valid b64 token", func(t *testing.T) { + f, err := os.CreateTemp(t.TempDir(), "auth-") + if err != nil { + t.Fatal(err) + } + + token := "secret!" + f.Write([]byte(token)) + f.Write([]byte("\n")) + f.Write([]byte(" antani\n")) + defer f.Close() + defer os.Remove(f.Name()) + + expected := "" + + line, err := readBearerTokenFromFile(f.Name()) + if line != expected { + t.Fatalf("expected empty string, got %s", line) + } + if err != nil { + t.Fatal("expected err==nil") + } + }) + }