optimized ipquality detector (#3971)

* optimized ipquality detector

* added extradata

* set verification error

* added explaination
This commit is contained in:
Kashif Khan
2025-03-21 15:54:36 +05:00
committed by GitHub
parent 82ea62c17e
commit 3d197dee03
2 changed files with 79 additions and 27 deletions
+74 -20
View File
@@ -2,12 +2,15 @@ package ipquality
import (
"context"
"encoding/json"
"errors"
"fmt"
regexp "github.com/wasilibs/go-re2"
"io"
"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"
@@ -22,9 +25,20 @@ var (
client = 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{"ipquality"}) + `\b([0-9a-z]{32})\b`)
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"ipquality"}) + `\b([0-9a-zA-Z]{32})\b`)
)
const (
// response messages
invalidKeyMessage = "Invalid or unauthorized key"
insufficientCreditMessage = "insufficient credits"
)
type apiResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
}
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
@@ -46,24 +60,10 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://www.ipqualityscore.com/api/json/account/%s", resMatch), nil)
if err != nil {
continue
}
req.Header.Add("Content-Type", "application/json")
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
bodyBytes, err := io.ReadAll(res.Body)
if err != nil {
continue
}
body := string(bodyBytes)
validResponse := strings.Contains(body, "insufficient credits") || strings.Contains(body, `"success":true`)
if (res.StatusCode >= 200 && res.StatusCode < 300) && validResponse {
s1.Verified = true
}
}
isVerified, verificationErr := verifyIPQualityAPIKey(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -79,3 +79,57 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string {
return "IPQualityScore provides tools to detect and prevent fraudulent activity. IPQualityScore API keys can be used to access their fraud prevention services."
}
func verifyIPQualityAPIKey(ctx context.Context, client *http.Client, apiKey string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://www.ipqualityscore.com/api/json/account/%s", apiKey), nil)
if err != nil {
return false, err
}
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
var response apiResponse
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return false, err
}
if err = json.Unmarshal(bodyBytes, &response); err != nil {
return false, err
}
switch response.Success {
case true:
return true, nil
case false:
/*
for invalid api key and for a key which has insufficient credit the API returns the same response.
The scenario where we have correct API key but it has insufficient credit is rare than a scenario that we capture
an invalid api key as the pattern is too common. Hence in case we get insufficient credit error message we mark the
API Key as inactive and send back a verification error as well.
*/
if strings.Contains(response.Message, insufficientCreditMessage) {
return false, errors.New("couldn't verify; API Key has " + insufficientCreditMessage)
} else if strings.Contains(response.Message, invalidKeyMessage) {
return false, nil
}
}
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
@@ -1,10 +1,8 @@
//go:build detectors
// +build detectors
package ipquality
import (
"context"
"errors"
"fmt"
"testing"
"time"
@@ -26,6 +24,9 @@ func TestIpquality_FromChunk(t *testing.T) {
secret := testSecrets.MustGetField("IPQUALITY")
inactiveSecret := testSecrets.MustGetField("IPQUALITY_INACTIVE")
invalidResult := detectors.Result{DetectorType: detectorspb.DetectorType_IPQuality, Verified: false}
invalidResult.SetVerificationError(errors.New("couldn't verify; API Key has " + insufficientCreditMessage))
type args struct {
ctx context.Context
data []byte
@@ -63,10 +64,7 @@ func TestIpquality_FromChunk(t *testing.T) {
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_IPQuality,
Verified: false,
},
invalidResult,
},
wantErr: false,
},