diff --git a/pkg/detectors/tableau/tableau.go b/pkg/detectors/tableau/tableau.go new file mode 100644 index 000000000..7bece3f11 --- /dev/null +++ b/pkg/detectors/tableau/tableau.go @@ -0,0 +1,263 @@ +package tableau + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "maps" + "net/http" + "strings" + + regexp "github.com/wasilibs/go-re2" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +type Scanner struct { + detectors.DefaultMultiPartCredentialProvider + detectors.EndpointSetter + client *http.Client +} + +// Ensure the Scanner satisfies the interface at compile time. +var _ detectors.Detector = (*Scanner)(nil) +var _ detectors.EndpointCustomizer = (*Scanner)(nil) + +func (Scanner) CloudEndpoint() string { return "" } + +var ( + defaultClient = common.SaneHttpClient() + + // Simplified token name pattern using PrefixRegex + tokenNamePat = regexp.MustCompile(detectors.PrefixRegex([]string{"pat", "token", "name", "tableau"}) + `([a-zA-Z][a-zA-Z0-9_-]{2,50})`) + + // Pattern for Personal Access Token Secrets + tokenSecretPat = regexp.MustCompile(`\b([A-Za-z0-9+/]{22}==:[A-Za-z0-9]{32})\b`) + + // Simplified Tableau Server URLs pattern + tableauURLPat = regexp.MustCompile(`\b([a-zA-Z0-9\-]+\.online\.tableau\.com)\b`) +) + +// Keywords are used for efficiently pre-filtering chunks. +func (s Scanner) Keywords() []string { + return []string{ + "tableau", + "online.tableau.com", + } +} + +func (s Scanner) getClient() *http.Client { + if s.client != nil { + return s.client + } + return defaultClient +} + +func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { + dataStr := string(data) + + // Extract token names, secrets, and URLs separately + tokenNames := extractTokenNames(dataStr) + tokenSecrets := extractTokenSecrets(dataStr) + foundURLs := extractTableauURLs(dataStr) + + // If no names or secrets found, return empty results + if len(tokenNames) == 0 || len(tokenSecrets) == 0 { + return results, nil + } + + // Use maps to deduplicate endpoints + var uniqueEndpoints = make(map[string]struct{}) + + // Add endpoints to the list + for _, endpoint := range s.Endpoints(foundURLs...) { + // Remove https:// prefix if present since we add it during verification + endpoint = strings.TrimPrefix(endpoint, "https://") + uniqueEndpoints[endpoint] = struct{}{} + } + + // Process each combination of token name, token secret, and endpoint + for _, tokenName := range tokenNames { + for _, tokenSecret := range tokenSecrets { + for endpoint := range uniqueEndpoints { + result := detectors.Result{ + DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken, + Raw: []byte(tokenName), + RawV2: []byte(fmt.Sprintf("%s:%s:%s", tokenName, tokenSecret, endpoint)), + ExtraData: make(map[string]string), + } + + if verify { + client := s.getClient() + isVerified, extraData, verificationErr := verifyTableauPAT(ctx, client, tokenName, tokenSecret, endpoint) + result.Verified = isVerified + maps.Copy(result.ExtraData, extraData) + result.SetVerificationError(verificationErr, tokenName, tokenSecret, endpoint) + } + results = append(results, result) + } + } + } + + return results, nil +} + +// extractTokenNames finds all potential token names in the data +func extractTokenNames(data string) []string { + var names []string + // Create a map of false positive terms + falsePositives := map[detectors.FalsePositive]struct{}{ + detectors.FalsePositive("com"): {}, + } + + for _, match := range tokenNamePat.FindAllStringSubmatch(data, -1) { + if len(match) >= 2 { + name := strings.TrimSpace(match[1]) + isFalsePositive, _ := detectors.IsKnownFalsePositive(name, falsePositives, false) + if !isFalsePositive { + names = append(names, name) + } + } + } + return names +} + +// extractTokenSecrets finds all potential token secrets in the data +func extractTokenSecrets(data string) []string { + var secrets []string + for _, match := range tokenSecretPat.FindAllStringSubmatch(data, -1) { + if len(match) >= 2 { + secret := strings.TrimSpace(match[1]) + secrets = append(secrets, secret) + } + } + return secrets +} + +// extractTableauURLs finds all potential Tableau server URLs in the data +func extractTableauURLs(data string) []string { + var urls []string + + for _, match := range tableauURLPat.FindAllStringSubmatch(data, -1) { + if len(match) >= 2 { + url := strings.TrimSpace(match[1]) + if url != "" { + urls = append(urls, url) + } + } + } + + return urls +} + +// TableauAuthRequest represents the authentication request structure +type TableauAuthRequest struct { + Credentials TableauCredentials `json:"credentials"` +} + +type TableauCredentials struct { + PersonalAccessTokenName string `json:"personalAccessTokenName"` + PersonalAccessTokenSecret string `json:"personalAccessTokenSecret"` + Site interface{} `json:"site"` +} + +// TableauAuthResponse represents the authentication response structure +type TableauAuthResponse struct { + Credentials struct { + Site struct { + ID string `json:"id"` + ContentURL string `json:"contentUrl"` + } `json:"site"` + User struct { + ID string `json:"id"` + } `json:"user"` + Token string `json:"token"` + } `json:"credentials"` +} + +// verifyTableauPAT verifies a Tableau Personal Access Token by attempting authentication +func verifyTableauPAT(ctx context.Context, client *http.Client, tokenName, tokenSecret, endpoint string) (bool, map[string]string, error) { + // Build the verification URL + verifyURL := fmt.Sprintf("https://%s/api/3.26/auth/signin", endpoint) + + // Prepare metadata early - before any potential errors + extraData := map[string]string{ + "verification_endpoint": verifyURL, + "verification_method": "tableau_pat_auth", + "tableau_endpoint": endpoint, + } + + // Rest of your verification logic... + authReq := TableauAuthRequest{ + Credentials: TableauCredentials{ + PersonalAccessTokenName: tokenName, + PersonalAccessTokenSecret: tokenSecret, + Site: map[string]interface{}{}, + }, + } + + // Marshal to JSON + jsonData, err := json.Marshal(authReq) + if err != nil { + return false, nil, fmt.Errorf("failed to marshal auth request: %v", err) + } + + // Create HTTP request + req, err := http.NewRequestWithContext(ctx, http.MethodPost, verifyURL, bytes.NewBuffer(jsonData)) + if err != nil { + return false, nil, fmt.Errorf("failed to create request: %v", err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + + // Execute request + resp, err := client.Do(req) + if err != nil { + // Check if it's a DNS/network error + if strings.Contains(err.Error(), "no such host") || + strings.Contains(err.Error(), "dial tcp") || + strings.Contains(err.Error(), "connection refused") { + extraData["network_error"] = "true" + return false, extraData, nil // No error, just invalid endpoint + } + return false, nil, fmt.Errorf("request failed: %v", err) + } + defer func() { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() + + // Read the response + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return false, nil, fmt.Errorf("failed to read response body: %v", err) + } + + // Status code handling... + switch resp.StatusCode { + case http.StatusOK: + var authResp TableauAuthResponse + if err := json.Unmarshal(bodyBytes, &authResp); err != nil { + return true, extraData, err + } + return true, extraData, nil + + case http.StatusUnauthorized, http.StatusBadRequest, http.StatusForbidden: + return false, extraData, nil + + default: + return false, extraData, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode) + } +} + +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_TableauPersonalAccessToken +} + +func (s Scanner) Description() string { + return "Tableau is a data visualization and business intelligence platform. Personal Access Tokens (PATs) provide programmatic access to Tableau Server/Online APIs and can be used to authenticate applications and automate workflows." +} diff --git a/pkg/detectors/tableau/tableau_integration_test.go b/pkg/detectors/tableau/tableau_integration_test.go new file mode 100644 index 000000000..c0ddd495b --- /dev/null +++ b/pkg/detectors/tableau/tableau_integration_test.go @@ -0,0 +1,266 @@ +//go:build detectors +// +build detectors + +package tableau + +import ( + "context" + "fmt" + + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +func TestTableau_FromChunk(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + + tokenName := testSecrets.MustGetField("TABLEAU_TOKEN_NAME") + tokenSecret := testSecrets.MustGetField("TABLEAU_TOKEN_SECRET") + inactiveTokenName := testSecrets.MustGetField("TABLEAU_INACTIVE_TOKEN_NAME") + inactiveTokenSecret := testSecrets.MustGetField("TABLEAU_INACTIVE_TOKEN_SECRET") + tableauURL := testSecrets.MustGetField("TABLEAU_VALID_POD_NAME") + invalidURL := testSecrets.MustGetField("TABLEAU_INVALID_POD_NAME") + + type args struct { + ctx context.Context + data []byte + verify bool + } + tests := []struct { + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool + }{ + { + name: "found, verified with URL", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("token=%s\nsecret=%s\nserver=%s", tokenName, tokenSecret, tableauURL)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken, + Verified: true, + ExtraData: map[string]string{ + "token_name": tokenName, + "token_secret": tokenSecret, + "endpoint": tableauURL, + "credential_type": "personal_access_token", + "verification_endpoint": "https://" + tableauURL + "/api/3.26/auth/signin", + "http_status": "200", + "verification_method": "tableau_pat_auth", + "verification_status": "valid", + "auth_token_received": "true", + }, + }, + }, + wantErr: false, + }, + { + name: "found, verified but unexpected api surface", + s: Scanner{client: common.ConstantResponseHttpClient(500, "Internal Server Error")}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("name = '%s'\n secret = '%s'\nserver = '%s'", tokenName, tokenSecret, tableauURL)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken, + Verified: false, + ExtraData: map[string]string{ + "token_name": tokenName, + "token_secret": tokenSecret, + "endpoint": tableauURL, + "credential_type": "personal_access_token", + "verification_endpoint": "https://" + tableauURL + "/api/3.26/auth/signin", + "http_status": "500", + "verification_method": "tableau_pat_auth", + "verification_status": "error", + }, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + { + name: "found, unverified with invalid URL", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("name = '%s'\nsecret = '%s'\nserver = '%s'", tokenName, tokenSecret, invalidURL)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken, + Verified: false, + ExtraData: map[string]string{ + "token_name": tokenName, + "token_secret": tokenSecret, + "endpoint": invalidURL, + "credential_type": "personal_access_token", + "verification_endpoint": "https://" + invalidURL + "/api/3.26/auth/signin", + "http_status": "404", // or whatever status invalid URL returns + "verification_method": "tableau_pat_auth", + "verification_status": "error", + }, + }, + }, + wantErr: false, + }, + { + name: "found, unverified with inactive token", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("name = '%s'\n secret = '%s'\nserver = '%s'", inactiveTokenName, inactiveTokenSecret, tableauURL)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken, + Verified: false, + ExtraData: map[string]string{ + "token_name": inactiveTokenName, + "token_secret": inactiveTokenSecret, + "endpoint": tableauURL, + "credential_type": "personal_access_token", + "verification_endpoint": "https://" + tableauURL + "/api/3.26/auth/signin", + "http_status": "401", + "verification_method": "tableau_pat_auth", + "verification_status": "invalid", + }, + }, + }, + wantErr: false, + }, + { + name: "not found", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte("You cannot find the tableau secret within"), + verify: true, + }, + want: nil, + wantErr: false, + }, + { + name: "found, unverified - malformed token", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte("tableau pat_name = 'TestToken'\ntableau pat_secret = 'malformed_secret_format'"), + verify: true, + }, + want: nil, // Should not find due to invalid secret format + wantErr: false, + }, + { + name: "found multiple, mixed verification results with URLs", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf(` + name1 = '%s' + name2 = '%s' + secret = '%s' + secret2 = '%s' + server1 = '%s' + server2 = '%s' + `, tokenName, inactiveTokenName, tokenSecret, inactiveTokenSecret, tableauURL, invalidURL)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken, + Verified: true, // tokenName + tokenSecret + valid URL + }, + { + DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken, + Verified: false, // tokenName + tokenSecret + invalid URL + }, + { + DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken, + Verified: false, // tokenName + inactiveTokenSecret + valid URL + }, + { + DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken, + Verified: false, // tokenName + inactiveTokenSecret + invalid URL + }, + { + DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken, + Verified: false, // inactiveTokenName + tokenSecret + valid URL + }, + { + DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken, + Verified: false, // inactiveTokenName + tokenSecret + invalid URL + }, + { + DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken, + Verified: false, // inactiveTokenName + inactiveTokenSecret + valid URL + }, + { + DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken, + Verified: false, // inactiveTokenName + inactiveTokenSecret + invalid URL + }, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Enable found endpoints for tests that need URL detection + scanner := tt.s + scanner.UseFoundEndpoints(true) + + got, err := scanner.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("Tableau.FromData() error = %v, wantErr %v", err, tt.wantErr) + return + } + + // Check that we got the expected number of results + if len(got) != len(tt.want) { + t.Errorf("Tableau.FromData() got %d results, want %d", len(got), len(tt.want)) + } + + for i := range got { + if len(got[i].Raw) == 0 { + t.Fatalf("no raw secret present: \n %+v", got[i]) + } + if (got[i].VerificationError() != nil) != tt.wantVerificationErr { + t.Errorf("Tableau.FromData() verificationError = %v, wantVerificationErr %v", got[i].VerificationError(), tt.wantVerificationErr) + } + } + + ignoreOpts := []cmp.Option{ + cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "ExtraData"), + cmpopts.IgnoreUnexported(detectors.Result{}), + } + + if diff := cmp.Diff(got, tt.want, ignoreOpts...); diff != "" { + t.Errorf("Tableau.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + } + }) + } +} diff --git a/pkg/detectors/tableau/tableau_test.go b/pkg/detectors/tableau/tableau_test.go new file mode 100644 index 000000000..2406a218c --- /dev/null +++ b/pkg/detectors/tableau/tableau_test.go @@ -0,0 +1,144 @@ +package tableau + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick" +) + +var ( + validPATName = "test-token-6" + validPATSecret = "YMqNfVWiTSa0QgpoJ9GpCw==:KTamjuKrXF5gVETjIJafBBvP8Ctj5aJ3" + invalidPATSecret = "invalid-secret-format" + // Tableau Online endpoints for testing + validTableauURL = "prod-ansouthgest-a.online.tableau.com" + valid2ndTableauURL = "prod.online.tableau.com" + invalidTableauURL = "prod.tabeau.com" +) + +func TestTableau_Pattern(t *testing.T) { + d := Scanner{} + d.UseFoundEndpoints(true) // Enable found endpoints for tests + ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d}) + + tests := []struct { + name string + input string + want []string + description string + }{ + { + name: "tableau_prefix_with_token_name", + input: fmt.Sprintf("token=%s\nsecret=%s\nserver=%s", validPATName, validPATSecret, validTableauURL), + want: []string{fmt.Sprintf("%s:%s:%s", validPATName, validPATSecret, validTableauURL)}, + description: "Tests tableau prefix directly followed by token name", + }, + { + name: "pat_prefix_with_name", + input: fmt.Sprintf("pat %s\n%s\nurl=%s", validPATName, validPATSecret, validTableauURL), + want: []string{fmt.Sprintf("%s:%s:%s", validPATName, validPATSecret, validTableauURL)}, + description: "Tests pat prefix triggering detection", + }, + { + name: "name_prefix_with_token", + input: fmt.Sprintf("name %s\n%s\nurl=%s", validPATName, validPATSecret, validTableauURL), + want: []string{fmt.Sprintf("%s:%s:%s", validPATName, validPATSecret, validTableauURL)}, + description: "Tests name prefix triggering detection", + }, + { + name: "single_token_multiple_urls", + input: fmt.Sprintf("token %s\n%s\nserver1=%s\nserver2=%s", validPATName, validPATSecret, validTableauURL, valid2ndTableauURL), + want: []string{ + fmt.Sprintf("%s:%s:%s", validPATName, validPATSecret, validTableauURL), + fmt.Sprintf("%s:%s:%s", validPATName, validPATSecret, valid2ndTableauURL), + }, + description: "Tests single token with multiple URLs", + }, + { + name: "invalid_secret_format", + input: fmt.Sprintf("tableau %s\n%s\nserver=%s", validPATName, invalidPATSecret, validTableauURL), + want: []string{}, + description: "Tests that invalid secret format is not detected", + }, + { + name: "invalid_tableau_url", + input: fmt.Sprintf("tableau %s\n%s\nserver=%s", validPATName, validPATSecret, invalidTableauURL), + want: []string{}, + description: "Tests that invalid Tableau URL is not detected when useFoundEndpoints is true", + }, + { + name: "missing_token_name", + input: fmt.Sprintf("\n%s\nserver=%s", validPATSecret, validTableauURL), + want: []string{}, + description: "Tests that missing token name produces no results", + }, + { + name: "missing_secret", + input: fmt.Sprintf("tableau %s\nserver=%s", validPATName, validTableauURL), + want: []string{}, + description: "Tests that missing secret produces no results", + }, + { + name: "no_tableau_keywords", + input: "username=test\npassword=secret123\nhost=example.com", + want: []string{}, + description: "Tests that non-Tableau config produces no results", + }, + { + name: "token_name_with_whitespace", + input: fmt.Sprintf("name %s\n%s\nserver=%s", validPATName, validPATSecret, validTableauURL), + want: []string{fmt.Sprintf("%s:%s:%s", validPATName, validPATSecret, validTableauURL)}, + description: "Tests token name with extra whitespace", + }, + { + name: "pat_with_single_quotes", + input: fmt.Sprintf("pat '%s'\n%s\nserver=%s", validPATName, validPATSecret, validTableauURL), + want: []string{fmt.Sprintf("%s:%s:%s", validPATName, validPATSecret, validTableauURL)}, + description: "Tests token name with single quotes", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input)) + if len(test.want) > 0 { + if len(matchedDetectors) == 0 { + t.Errorf("keywords '%v' not matched in input: %s", d.Keywords(), test.input) + return + } + } + + if len(matchedDetectors) == 0 && len(test.want) == 0 { + return + } + + results, err := d.FromData(context.Background(), false, []byte(test.input)) + if err != nil { + t.Errorf("FromData error: %v", err) + return + } + + if len(results) != len(test.want) { + t.Errorf("expected %d results, got %d", len(test.want), len(results)) + return + } + + actual := make(map[string]struct{}, len(results)) + for _, r := range results { + actual[string(r.RawV2)] = struct{}{} + } + expected := make(map[string]struct{}, len(test.want)) + for _, v := range test.want { + expected[v] = struct{}{} + } + + if diff := cmp.Diff(expected, actual); diff != "" { + t.Errorf("%s diff: (-want +got)\n%s", test.name, diff) + } + }) + } +} diff --git a/pkg/engine/defaults/defaults.go b/pkg/engine/defaults/defaults.go index 5a5e1bb6e..ad8d25a00 100644 --- a/pkg/engine/defaults/defaults.go +++ b/pkg/engine/defaults/defaults.go @@ -725,6 +725,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/survicate" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/swell" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/swiftype" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tableau" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tailscale" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tallyfy" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tatumio" @@ -1723,6 +1724,10 @@ func buildDetectorList() []detectors.Detector { &zohocrm.Scanner{}, &zonkafeedback.Scanner{}, &zulipchat.Scanner{}, + &stripepaymentintent.Scanner{}, + &tableau.Scanner{}, + &bitbucketapppassword.Scanner{}, + &hasura.Scanner{}, } } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index d78804c56..4e2888e99 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -1228,7 +1228,7 @@ func TestEngineInitializesCloudProviderDetectors(t *testing.T) { for _, det := range e.detectors { if endpoints, ok := det.(interface{ Endpoints(...string) []string }); ok { id := config.GetDetectorID(det) - if len(endpoints.Endpoints()) == 0 && det.Type() != detectorspb.DetectorType_ArtifactoryAccessToken { // artifactory does not have any cloud endpoint + if len(endpoints.Endpoints()) == 0 && det.Type() != detectorspb.DetectorType_ArtifactoryAccessToken && det.Type() != detectorspb.DetectorType_TableauPersonalAccessToken { // artifactory and tableau does not have any cloud endpoint t.Fatalf("detector %q Endpoints() is empty", id.String()) } count++ diff --git a/pkg/pb/detectorspb/detectors.pb.go b/pkg/pb/detectorspb/detectors.pb.go index 95681b788..0dd854692 100644 --- a/pkg/pb/detectorspb/detectors.pb.go +++ b/pkg/pb/detectorspb/detectors.pb.go @@ -1140,6 +1140,7 @@ const ( DetectorType_SalesforceRefreshToken DetectorType = 1031 DetectorType_AnypointOAuth2 DetectorType = 1032 DetectorType_WebexBot DetectorType = 1033 + DetectorType_TableauPersonalAccessToken DetectorType = 1034 ) // Enum value maps for DetectorType. @@ -2175,6 +2176,7 @@ var ( 1031: "SalesforceRefreshToken", 1032: "AnypointOAuth2", 1033: "WebexBot", + 1034: "TableauPersonalAccessToken", } DetectorType_value = map[string]int32{ "Alibaba": 0, @@ -3207,6 +3209,7 @@ var ( "SalesforceRefreshToken": 1031, "AnypointOAuth2": 1032, "WebexBot": 1033, + "TableauPersonalAccessToken": 1034, } ) @@ -3660,7 +3663,7 @@ var file_detectors_proto_rawDesc = []byte{ 0x4c, 0x41, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x41, 0x53, 0x45, 0x36, 0x34, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x54, 0x46, 0x31, 0x36, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x45, 0x53, 0x43, 0x41, 0x50, 0x45, 0x44, 0x5f, 0x55, 0x4e, 0x49, 0x43, 0x4f, 0x44, 0x45, - 0x10, 0x04, 0x2a, 0xcb, 0x85, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x10, 0x04, 0x2a, 0xec, 0x85, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x4d, 0x51, 0x50, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x10, 0x03, 0x12, @@ -4729,11 +4732,13 @@ var file_detectors_proto_rawDesc = []byte{ 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x87, 0x08, 0x12, 0x13, 0x0a, 0x0e, 0x41, 0x6e, 0x79, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x32, 0x10, 0x88, 0x08, 0x12, 0x0d, 0x0a, 0x08, 0x57, 0x65, 0x62, 0x65, 0x78, 0x42, 0x6f, 0x74, 0x10, 0x89, - 0x08, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, - 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, - 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x70, 0x62, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x08, 0x12, 0x1f, 0x0a, 0x1a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x50, 0x65, 0x72, 0x73, + 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, + 0x8a, 0x08, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, + 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x33, 0x2f, 0x70, + 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x70, + 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/detectors.proto b/proto/detectors.proto index 9bffa9e61..55703b1de 100644 --- a/proto/detectors.proto +++ b/proto/detectors.proto @@ -1043,6 +1043,7 @@ enum DetectorType { SalesforceRefreshToken = 1031; AnypointOAuth2 = 1032; WebexBot = 1033; + TableauPersonalAccessToken = 1034; } message Result {