Skip to content

Commit e819d90

Browse files
nabeelalamamanfcp
andauthored
[Feature] Updated Dotmailer Detector To Dotdigital (#4331)
* updated dotmailer detector; renamed to dotdigital * temp * temp * updated dotmailer name, verification method and tests --------- Co-authored-by: Amaan Ullah <[email protected]>
1 parent 9005cf9 commit e819d90

File tree

7 files changed

+908
-865
lines changed

7 files changed

+908
-865
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package dotdigital
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"time"
9+
10+
regexp "github.com/wasilibs/go-re2"
11+
12+
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
13+
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
14+
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
15+
)
16+
17+
type Scanner struct {
18+
client *http.Client
19+
detectors.DefaultMultiPartCredentialProvider
20+
}
21+
22+
// Ensure the Scanner satisfies the interface at compile time.
23+
var _ detectors.Detector = (*Scanner)(nil)
24+
25+
var (
26+
defaultClient = common.SaneHttpClient()
27+
28+
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
29+
emailPat = regexp.MustCompile(`\b(apiuser-[a-z0-9]{12}@apiconnector.com)\b`)
30+
passPat = regexp.MustCompile(detectors.PrefixRegex([]string{"pw", "pass"}) + `\b([a-zA-Z0-9\S]{8,24})\b`)
31+
)
32+
33+
// Keywords are used for efficiently pre-filtering chunks.
34+
// Use identifiers in the secret preferably, or the provider name.
35+
func (s Scanner) Keywords() []string {
36+
return []string{"@apiconnector.com"}
37+
}
38+
39+
func (s Scanner) getClient() *http.Client {
40+
if s.client != nil {
41+
return s.client
42+
}
43+
return defaultClient
44+
}
45+
46+
// FromData will find and optionally verify Dotdigital secrets in a given set of bytes.
47+
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
48+
dataStr := string(data)
49+
50+
var uniqueEmails, uniquePasswords = make(map[string]struct{}), make(map[string]struct{})
51+
52+
for _, matches := range emailPat.FindAllStringSubmatch(dataStr, -1) {
53+
uniqueEmails[matches[1]] = struct{}{}
54+
}
55+
for _, matches := range passPat.FindAllStringSubmatch(dataStr, -1) {
56+
uniquePasswords[matches[1]] = struct{}{}
57+
}
58+
59+
for email := range uniqueEmails {
60+
for password := range uniquePasswords {
61+
s1 := detectors.Result{
62+
DetectorType: detectorspb.DetectorType_Dotdigital,
63+
Raw: []byte(email),
64+
RawV2: []byte(email + password),
65+
}
66+
67+
if verify {
68+
client := s.getClient()
69+
isVerified, verificationErr := verifyMatch(ctx, client, email, password)
70+
s1.Verified = isVerified
71+
s1.SetVerificationError(verificationErr)
72+
}
73+
74+
results = append(results, s1)
75+
76+
if s1.Verified {
77+
// Once the email is verified, we can stop checking other passwords for it.
78+
break
79+
}
80+
}
81+
}
82+
return results, nil
83+
}
84+
85+
func verifyMatch(ctx context.Context, client *http.Client, email, pass string) (bool, error) {
86+
// Reference: https://developer.dotdigital.com/reference/get-account-information
87+
88+
timeout := 10 * time.Second
89+
client.Timeout = timeout
90+
url := "https://r1-api.dotdigital.com/v2/account-info"
91+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
92+
if err != nil {
93+
return false, err
94+
}
95+
96+
req.SetBasicAuth(email, pass)
97+
res, err := client.Do(req)
98+
if err != nil {
99+
return false, err
100+
}
101+
102+
defer func() {
103+
_, _ = io.Copy(io.Discard, res.Body)
104+
_ = res.Body.Close()
105+
}()
106+
107+
switch res.StatusCode {
108+
case http.StatusOK:
109+
return true, nil
110+
case http.StatusUnauthorized:
111+
return false, nil
112+
default:
113+
return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
114+
}
115+
}
116+
117+
func (s Scanner) Type() detectorspb.DetectorType {
118+
return detectorspb.DetectorType_Dotdigital
119+
}
120+
121+
func (s Scanner) Description() string {
122+
return "Dotdigital is an email marketing automation platform. API keys can be used to access and manage email campaigns and related data."
123+
}

pkg/detectors/dotmailer/dotmailer_integration_test.go renamed to pkg/detectors/dotdigital/dotdigital_integration_test.go

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,32 @@
11
//go:build detectors
22
// +build detectors
33

4-
package dotmailer
4+
package dotdigital
55

66
import (
77
"context"
88
"fmt"
99
"testing"
1010
"time"
1111

12-
"github.com/kylelemons/godebug/pretty"
12+
"github.com/google/go-cmp/cmp"
13+
"github.com/google/go-cmp/cmp/cmpopts"
1314
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
1415

1516
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
1617
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
1718
)
1819

19-
func TestDotmailer_FromChunk(t *testing.T) {
20+
func TestDotdigital_FromChunk(t *testing.T) {
2021
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
2122
defer cancel()
22-
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
23+
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
2324
if err != nil {
2425
t.Fatalf("could not get test secrets from GCP: %s", err)
2526
}
26-
secret := testSecrets.MustGetField("DOTMAILER")
27-
pass := testSecrets.MustGetField("DOTMAILER_PASS")
28-
inactiveSecret := testSecrets.MustGetField("DOTMAILER_INACTIVE")
27+
email := testSecrets.MustGetField("DOTDIGITAL_EMAIL")
28+
password := testSecrets.MustGetField("DOTDIGITAL_PASSWORD")
29+
inactivePassword := testSecrets.MustGetField("DOTDIGITAL_INACTIVE")
2930

3031
type args struct {
3132
ctx context.Context
@@ -44,12 +45,12 @@ func TestDotmailer_FromChunk(t *testing.T) {
4445
s: Scanner{},
4546
args: args{
4647
ctx: context.Background(),
47-
data: []byte(fmt.Sprintf("You can find a dotmailer secret %s within dotmailer pass %s", secret, pass)),
48+
data: []byte(fmt.Sprintf("You can find a dotdigital user %s within dotdigital pass %s", email, password)),
4849
verify: true,
4950
},
5051
want: []detectors.Result{
5152
{
52-
DetectorType: detectorspb.DetectorType_Dotmailer,
53+
DetectorType: detectorspb.DetectorType_Dotdigital,
5354
Verified: true,
5455
},
5556
},
@@ -60,12 +61,12 @@ func TestDotmailer_FromChunk(t *testing.T) {
6061
s: Scanner{},
6162
args: args{
6263
ctx: context.Background(),
63-
data: []byte(fmt.Sprintf("You can find a dotmailer secret %s within dotmailer pass %s but not valid ", inactiveSecret, pass)), // the secret would satisfy the regex but not pass validation
64+
data: []byte(fmt.Sprintf("You can find a dotdigital user %s within dotdigital pass %s but not valid ", email, inactivePassword)), // the secret would satisfy the regex but not pass validation
6465
verify: true,
6566
},
6667
want: []detectors.Result{
6768
{
68-
DetectorType: detectorspb.DetectorType_Dotmailer,
69+
DetectorType: detectorspb.DetectorType_Dotdigital,
6970
Verified: false,
7071
},
7172
},
@@ -88,17 +89,28 @@ func TestDotmailer_FromChunk(t *testing.T) {
8889
s := Scanner{}
8990
got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
9091
if (err != nil) != tt.wantErr {
91-
t.Errorf("Dotmailer.FromData() error = %v, wantErr %v", err, tt.wantErr)
92+
t.Errorf("Dotdigital.FromData() error = %v, wantErr %v", err, tt.wantErr)
9293
return
9394
}
9495
for i := range got {
9596
if len(got[i].Raw) == 0 {
96-
t.Fatal("no raw secret present")
97+
t.Fatalf("no raw secret present: \n %+v", got[i])
98+
}
99+
gotErr := ""
100+
if got[i].VerificationError() != nil {
101+
gotErr = got[i].VerificationError().Error()
102+
}
103+
wantErr := ""
104+
if tt.want[i].VerificationError() != nil {
105+
wantErr = tt.want[i].VerificationError().Error()
106+
}
107+
if gotErr != wantErr {
108+
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.want[i].VerificationError(), got[i].VerificationError())
97109
}
98-
got[i].Raw = nil
99110
}
100-
if diff := pretty.Compare(got, tt.want); diff != "" {
101-
t.Errorf("Dotmailer.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
111+
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "primarySecret")
112+
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
113+
t.Errorf("Dotdigital.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
102114
}
103115
})
104116
}

pkg/detectors/dotmailer/dotmailer_test.go renamed to pkg/detectors/dotdigital/dotdigital_test.go

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package dotmailer
1+
package dotdigital
22

33
import (
44
"context"
@@ -16,29 +16,21 @@ var (
1616
database:
1717
host: $DB_HOST
1818
port: $DB_PORT
19-
username: $DB_USERNAME
20-
password: $DB_PASS # IMPORTANT: Do not share this password publicly
2119
2220
api:
2321
auth_type: "Basic"
24-
in: "Path"
25-
api_version: v1
26-
dotmailer_key: "apiuser-trq6zw9mmdlt@apiconnector@com"
27-
dotmailer_secret: "N{w44mqa'2si(zY8"
22+
dotdigital_email: "[email protected]"
23+
dotdigital_password: "N{w44mqa'2si(zY8"
2824
base_url: "https://api.example.com/$api_version/example"
2925
response_code: 200
3026
3127
# Notes:
32-
# - Remember to rotate the secret every 90 days.
3328
# - The above credentials should only be used in a secure environment.
3429
`
35-
secrets = []string{
36-
"apiuser-trq6zw9mmdlt@apiconnector@comN{w44mqa'2si(zY8",
37-
"apiuser-trq6zw9mmdlt@apiconnector@comapiuser-trq6zw9mmdlt@",
38-
}
30+
secrets = []string{"[email protected]{w44mqa'2si(zY8"}
3931
)
4032

41-
func TestDotMailer_Pattern(t *testing.T) {
33+
func TestDotdigital_Pattern(t *testing.T) {
4234
d := Scanner{}
4335
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
4436

pkg/detectors/dotmailer/dotmailer.go

Lines changed: 0 additions & 84 deletions
This file was deleted.

pkg/engine/defaults/defaults.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ import (
237237
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/documo"
238238
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/docusign"
239239
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/doppler"
240-
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dotmailer"
240+
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dotdigital"
241241
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dovico"
242242
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dronahq"
243243
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/droneci"
@@ -1101,7 +1101,7 @@ func buildDetectorList() []detectors.Detector {
11011101
&documo.Scanner{},
11021102
&docusign.Scanner{},
11031103
&doppler.Scanner{},
1104-
&dotmailer.Scanner{},
1104+
&dotdigital.Scanner{},
11051105
&dovico.Scanner{},
11061106
&dronahq.Scanner{},
11071107
&droneci.Scanner{},

0 commit comments

Comments
 (0)