Skip to content

Commit a05cf08

Browse files
[Update] Dovico Detector Updated (#4290)
* updated verification method in the dovico detector * updated dovico detector pattern test * added RawV2 value in dovico detector result * updated dovico detector pattern test * updated comment in dovico detector --------- Co-authored-by: Shahzad Haider <[email protected]>
1 parent 227d92c commit a05cf08

File tree

3 files changed

+76
-29
lines changed

3 files changed

+76
-29
lines changed

pkg/detectors/dovico/dovico.go

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package dovico
33
import (
44
"context"
55
"fmt"
6+
"io"
67
"net/http"
7-
"strings"
88

99
regexp "github.com/wasilibs/go-re2"
1010

@@ -14,14 +14,15 @@ import (
1414
)
1515

1616
type Scanner struct {
17+
client *http.Client
1718
detectors.DefaultMultiPartCredentialProvider
1819
}
1920

2021
// Ensure the Scanner satisfies the interface at compile time.
2122
var _ detectors.Detector = (*Scanner)(nil)
2223

2324
var (
24-
client = common.SaneHttpClient()
25+
defaultClient = common.SaneHttpClient()
2526

2627
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
2728
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"dovico"}) + `\b([0-9a-z]{32}\.[0-9a-z]{1,}\b)`)
@@ -34,46 +35,85 @@ func (s Scanner) Keywords() []string {
3435
return []string{"dovico"}
3536
}
3637

38+
func (s Scanner) getClient() *http.Client {
39+
if s.client != nil {
40+
return s.client
41+
}
42+
return defaultClient
43+
}
44+
3745
// FromData will find and optionally verify Dovico secrets in a given set of bytes.
3846
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
3947
dataStr := string(data)
4048

41-
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
42-
userMatches := userPat.FindAllStringSubmatch(dataStr, -1)
49+
uniqueKeys := make(map[string]struct{})
50+
for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
51+
uniqueKeys[matches[1]] = struct{}{}
52+
}
4353

44-
for _, match := range matches {
45-
resMatch := strings.TrimSpace(match[1])
46-
for _, user := range userMatches {
47-
resUser := strings.TrimSpace(user[1])
54+
uniqueUserKeys := make(map[string]struct{})
55+
for _, matches := range userPat.FindAllStringSubmatch(dataStr, -1) {
56+
uniqueUserKeys[matches[1]] = struct{}{}
57+
}
58+
59+
for key := range uniqueKeys {
60+
for userKey := range uniqueUserKeys {
61+
if key == userKey {
62+
continue // Skip if ID and secret are the same.
63+
}
4864

4965
s1 := detectors.Result{
5066
DetectorType: detectorspb.DetectorType_Dovico,
51-
Raw: []byte(resMatch),
67+
Raw: []byte(key),
68+
RawV2: []byte(fmt.Sprintf("%s:%s", key, userKey)),
5269
}
5370

5471
if verify {
55-
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.dovico.com/Employees/?version=7", nil)
56-
if err != nil {
57-
continue
58-
}
59-
req.Header.Add("Content-Type", "application/json")
60-
req.Header.Add("Authorization", fmt.Sprintf(`WRAP access_token="client=%s&user_token=%s"`, resMatch, resUser))
61-
res, err := client.Do(req)
62-
if err == nil {
63-
defer res.Body.Close()
64-
if res.StatusCode >= 200 && res.StatusCode < 300 {
65-
s1.Verified = true
66-
}
67-
}
72+
client := s.getClient()
73+
isVerified, err := verifyMatch(ctx, client, key, userKey)
74+
s1.Verified = isVerified
75+
s1.SetVerificationError(err, key, userKey)
6876
}
6977

7078
results = append(results, s1)
79+
80+
// Credentials have 1:1 mapping so we can stop checking other user keys once it is verified
81+
if s1.Verified {
82+
break
83+
}
7184
}
7285
}
7386

7487
return results, nil
7588
}
7689

90+
func verifyMatch(ctx context.Context, client *http.Client, key, user string) (bool, error) {
91+
// Reference: https://timesheet.dovico.com/developer/API_doc/#t=API_Overview.html
92+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.dovico.com/employees/?version=7", http.NoBody)
93+
if err != nil {
94+
return false, err
95+
}
96+
req.Header.Add("Content-Type", "application/json")
97+
req.Header.Add("Authorization", fmt.Sprintf(`WRAP access_token="client=%s&user_token=%s"`, key, user))
98+
res, err := client.Do(req)
99+
if err != nil {
100+
return false, err
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+
77117
func (s Scanner) Type() detectorspb.DetectorType {
78118
return detectorspb.DetectorType_Dovico
79119
}

pkg/detectors/dovico/dovico_integration_test.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
func TestDovico_FromChunk(t *testing.T) {
2020
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
2121
defer cancel()
22-
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
22+
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
2323
if err != nil {
2424
t.Fatalf("could not get test secrets from GCP: %s", err)
2525
}
@@ -53,6 +53,10 @@ func TestDovico_FromChunk(t *testing.T) {
5353
DetectorType: detectorspb.DetectorType_Dovico,
5454
Verified: true,
5555
},
56+
{
57+
DetectorType: detectorspb.DetectorType_Dovico,
58+
Verified: false,
59+
},
5660
},
5761
wantErr: false,
5862
},
@@ -69,6 +73,10 @@ func TestDovico_FromChunk(t *testing.T) {
6973
DetectorType: detectorspb.DetectorType_Dovico,
7074
Verified: false,
7175
},
76+
{
77+
DetectorType: detectorspb.DetectorType_Dovico,
78+
Verified: false,
79+
},
7280
},
7381
wantErr: false,
7482
},
@@ -97,6 +105,7 @@ func TestDovico_FromChunk(t *testing.T) {
97105
t.Fatalf("no raw secret present: \n %+v", got[i])
98106
}
99107
got[i].Raw = nil
108+
got[i].RawV2 = nil
100109
}
101110
if diff := pretty.Compare(got, tt.want); diff != "" {
102111
t.Errorf("Dovico.FromData() %s diff: (-got +want)\n%s", tt.name, diff)

pkg/detectors/dovico/dovico_test.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ var (
2323
auth_type: "Token"
2424
in: "Header"
2525
api_version: v1
26-
dovico_user: "ntb4fnhk5iot7hzbfjw08jm661iocdd4.3ws4olz2l5jzw54yv3ai0qwdri6l1f4iyruc7f"
27-
dovico_token: "nuhkw7nsrybuvmetium29a6oajxr3xdg.sbpi6evkkrqz3onrg2epqj9i2lgkb0wxf8lq0gdzvw6macc9br1qi9ry335u173dr3gzcgy9v6"
26+
dovico_user: "ntb4fnhk5iot7hzbfjw08jm661iocdd4.3ws4ol"
27+
dovico_token: "nuhkw7nsrybuvmetium29a6oajxr3xdg.sbpi6e"
2828
base_url: "https://api.example.com/$api_version/example"
2929
response_code: 200
3030
@@ -33,10 +33,8 @@ var (
3333
# - The above credentials should only be used in a secure environment.
3434
`
3535
secrets = []string{
36-
"nuhkw7nsrybuvmetium29a6oajxr3xdg.sbpi6evkkrqz3onrg2epqj9i2lgkb0wxf8lq0gdzvw6macc9br1qi9ry335u173dr3gzcgy9v6",
37-
"nuhkw7nsrybuvmetium29a6oajxr3xdg.sbpi6evkkrqz3onrg2epqj9i2lgkb0wxf8lq0gdzvw6macc9br1qi9ry335u173dr3gzcgy9v6",
38-
"ntb4fnhk5iot7hzbfjw08jm661iocdd4.3ws4olz2l5jzw54yv3ai0qwdri6l1f4iyruc7f",
39-
"ntb4fnhk5iot7hzbfjw08jm661iocdd4.3ws4olz2l5jzw54yv3ai0qwdri6l1f4iyruc7f",
36+
"nuhkw7nsrybuvmetium29a6oajxr3xdg.sbpi6e:ntb4fnhk5iot7hzbfjw08jm661iocdd4.3ws4ol",
37+
"ntb4fnhk5iot7hzbfjw08jm661iocdd4.3ws4ol:nuhkw7nsrybuvmetium29a6oajxr3xdg.sbpi6e",
4038
}
4139
)
4240

0 commit comments

Comments
 (0)