diff --git a/pkg/detectors/alegra/alegra.go b/pkg/detectors/alegra/alegra.go index 1fcbfcb2bdce..856d77237681 100644 --- a/pkg/detectors/alegra/alegra.go +++ b/pkg/detectors/alegra/alegra.go @@ -2,6 +2,8 @@ package alegra import ( "context" + "fmt" + "io" "net/http" "strings" @@ -13,18 +15,17 @@ import ( ) type Scanner struct { - detectors.DefaultMultiPartCredentialProvider + client *http.Client } // Ensure the Scanner satisfies the interface at compile time. var _ detectors.Detector = (*Scanner)(nil) var ( - client = common.SaneHttpClient() - + defaultClient = common.SaneHttpClient() // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"alegra"}) + `\b([a-z0-9-]{20})\b`) - idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"alegra"}) + `\b([a-zA-Z0-9\.\-\@]{25,30})\b`) + idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"alegra"}) + common.EmailPattern) ) // Keywords are used for efficiently pre-filtering chunks. @@ -37,41 +38,38 @@ func (s Scanner) Keywords() []string { func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { dataStr := string(data) - matches := keyPat.FindAllStringSubmatch(dataStr, -1) + keyMatches := keyPat.FindAllStringSubmatch(dataStr, -1) idMatches := idPat.FindAllStringSubmatch(dataStr, -1) - for _, match := range matches { - if len(match) != 2 { - continue - } - tokenPatMatch := strings.TrimSpace(match[1]) + uniqueTokens := make(map[string]struct{}) + uniqueIDs := make(map[string]struct{}) - for _, idMatch := range idMatches { - if len(idMatch) != 2 { - continue - } + for _, match := range keyMatches { + uniqueTokens[match[1]] = struct{}{} + } - userPatMatch := strings.TrimSpace(idMatch[1]) + for _, match := range idMatches { + id := match[0][strings.LastIndex(match[0], " ")+1:] + uniqueIDs[id] = struct{}{} + } + for token := range uniqueTokens { + for id := range uniqueIDs { s1 := detectors.Result{ DetectorType: detectorspb.DetectorType_Alegra, - Raw: []byte(tokenPatMatch), - RawV2: []byte(tokenPatMatch + userPatMatch), + Raw: []byte(token), + RawV2: []byte(token + ":" + id), } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://api.alegra.com/api/v1/users", nil) - if err != nil { - continue - } - req.SetBasicAuth(userPatMatch, tokenPatMatch) - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - s1.Verified = true - } + client := s.client + if client == nil { + client = defaultClient } + + isVerified, verificationErr := verifyCredentials(ctx, client, id, token) + s1.Verified = isVerified + s1.SetVerificationError(verificationErr, token) } results = append(results, s1) @@ -81,6 +79,32 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyCredentials(ctx context.Context, client *http.Client, username, token string) (bool, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.alegra.com/api/v1/users/self", nil) + if err != nil { + return false, nil + } + req.SetBasicAuth(username, token) + + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + + switch res.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusUnauthorized: + return false, nil + default: + return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_Alegra } diff --git a/pkg/detectors/alegra/alegra_integration_test.go b/pkg/detectors/alegra/alegra_integration_test.go index 19913d9c8aeb..35961518bcb9 100644 --- a/pkg/detectors/alegra/alegra_integration_test.go +++ b/pkg/detectors/alegra/alegra_integration_test.go @@ -96,6 +96,7 @@ func TestAlegra_FromChunk(t *testing.T) { t.Fatalf("no raw secret present: \n %+v", got[i]) } got[i].Raw = nil + got[i].RawV2 = nil } if diff := pretty.Compare(got, tt.want); diff != "" { t.Errorf("Alegra.FromData() %s diff: (-got +want)\n%s", tt.name, diff) diff --git a/pkg/detectors/alegra/alegra_test.go b/pkg/detectors/alegra/alegra_test.go index d2eb6e3469a6..5594c9ba4c1f 100644 --- a/pkg/detectors/alegra/alegra_test.go +++ b/pkg/detectors/alegra/alegra_test.go @@ -14,7 +14,7 @@ import ( var ( validPattern = "wdvn-usa87a-fxp9ioas/testUser.1005@example.com" validSpecialCharPattern = "wdvn-usa87a-fxp9ioas / test-User.1005@example.com" - invalidPattern = "wdvn-usa87a-fxp9ioas/testUser$1005@example.com" + invalidPattern = "wdvn-usa87a-fxp9ioasQQsstestUsQQ@example" ) func TestAlegra_Pattern(t *testing.T) { @@ -28,22 +28,22 @@ func TestAlegra_Pattern(t *testing.T) { }{ { name: "valid pattern", - input: fmt.Sprintf("alegra: '%s'", validPattern), - want: []string{"wdvn-usa87a-fxp9ioastestUser.1005@example.com"}, + input: fmt.Sprintf("alegra: %s", validPattern), + want: []string{"wdvn-usa87a-fxp9ioas:wdvn-usa87a-fxp9ioas/testUser.1005@example.com"}, }, { name: "valid pattern - with special characters", - input: fmt.Sprintf("alegra: '%s'", validSpecialCharPattern), - want: []string{"wdvn-usa87a-fxp9ioastest-User.1005@example.com"}, + input: fmt.Sprintf("alegra: %s", validSpecialCharPattern), + want: []string{"wdvn-usa87a-fxp9ioas:test-User.1005@example.com"}, }, { name: "valid pattern - key out of prefix range", - input: fmt.Sprintf("alegra keyword is not close to the real key and id = '%s'", validPattern), + input: fmt.Sprintf("alegra keyword is not close to the real key and id = %s", validPattern), want: nil, }, { name: "invalid pattern", - input: fmt.Sprintf("alegra: '%s'", invalidPattern), + input: fmt.Sprintf("alegra: %s", invalidPattern), want: nil, }, } @@ -51,7 +51,7 @@ func TestAlegra_Pattern(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input)) - if len(matchedDetectors) == 0 { + if len(matchedDetectors) == 0 && test.want != nil { t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input) return } @@ -63,11 +63,7 @@ func TestAlegra_Pattern(t *testing.T) { } if len(results) != len(test.want) { - if len(results) == 0 { - t.Errorf("did not receive result") - } else { - t.Errorf("expected %d results, only received %d", len(test.want), len(results)) - } + t.Errorf("expected %d results, got %d", len(test.want), len(results)) return }