@@ -3,8 +3,8 @@ package dovico
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "io"
6
7
"net/http"
7
- "strings"
8
8
9
9
regexp "github.com/wasilibs/go-re2"
10
10
@@ -14,14 +14,15 @@ import (
14
14
)
15
15
16
16
type Scanner struct {
17
+ client * http.Client
17
18
detectors.DefaultMultiPartCredentialProvider
18
19
}
19
20
20
21
// Ensure the Scanner satisfies the interface at compile time.
21
22
var _ detectors.Detector = (* Scanner )(nil )
22
23
23
24
var (
24
- client = common .SaneHttpClient ()
25
+ defaultClient = common .SaneHttpClient ()
25
26
26
27
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
27
28
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 {
34
35
return []string {"dovico" }
35
36
}
36
37
38
+ func (s Scanner ) getClient () * http.Client {
39
+ if s .client != nil {
40
+ return s .client
41
+ }
42
+ return defaultClient
43
+ }
44
+
37
45
// FromData will find and optionally verify Dovico secrets in a given set of bytes.
38
46
func (s Scanner ) FromData (ctx context.Context , verify bool , data []byte ) (results []detectors.Result , err error ) {
39
47
dataStr := string (data )
40
48
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
+ }
43
53
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
+ }
48
64
49
65
s1 := detectors.Result {
50
66
DetectorType : detectorspb .DetectorType_Dovico ,
51
- Raw : []byte (resMatch ),
67
+ Raw : []byte (key ),
68
+ RawV2 : []byte (fmt .Sprintf ("%s:%s" , key , userKey )),
52
69
}
53
70
54
71
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 )
68
76
}
69
77
70
78
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
+ }
71
84
}
72
85
}
73
86
74
87
return results , nil
75
88
}
76
89
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
+
77
117
func (s Scanner ) Type () detectorspb.DetectorType {
78
118
return detectorspb .DetectorType_Dovico
79
119
}
0 commit comments