Skip to content

Commit b140e2b

Browse files
mustansir14shahzadhaider1amanfcpkashifkhan0771
authored
Detect Organization ID to pass into AnalysisInfo for Atlassian Detector (#4480)
* detect organization id and pass into analysis info * add non integration tests for analysis info, examples of credentials matching the regexes * switch from tabular tests to separate tests * move gock setup outside test functions --------- Co-authored-by: Shahzad Haider <[email protected]> Co-authored-by: Amaan Ullah <[email protected]> Co-authored-by: Kashif Khan <[email protected]>
1 parent d005847 commit b140e2b

File tree

3 files changed

+128
-21
lines changed

3 files changed

+128
-21
lines changed

pkg/detectors/atlassian/v2/atlassian.go

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ var _ detectors.Versioner = (*Scanner)(nil)
3535
var (
3636
defaultClient = common.SaneHttpClient()
3737
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
38+
39+
// Example: ATCTT3xFfGN0GsZNgOGrQSHSnxiJVi00oHlRicyM0yMNuKCBfw6qOHVcCy4Hm89GnclGb_W-1qAkxqCn5XbuyoX54bNhpK5yFKGFR7ocV6FByvL_P9Sb3tFnbUg3T3I3S_RGCBLMSN7Nsa4GJv8JEJ6bzvDmX-oJ8AnrazMU-zZ5hb-u3t2ERew=366BFE3A
3840
keyPat = regexp.MustCompile(`\b(ATCTT3xFfG[A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8})\b`)
41+
// Example: 123e4567-e89b-12d3-a456-426614174000
42+
organizationIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{"org", "id"}) + `\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\b`)
3943
)
4044

4145
// Keywords are used for efficiently pre-filtering chunks.
@@ -58,31 +62,52 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
5862
uniqueMatches[match[1]] = struct{}{}
5963
}
6064

61-
for match := range uniqueMatches {
62-
s1 := detectors.Result{
63-
DetectorType: detectorspb.DetectorType_Atlassian,
64-
Raw: []byte(match),
65-
ExtraData: map[string]string{
66-
"rotation_guide": "https://howtorotate.com/docs/tutorials/atlassian/",
67-
"version": fmt.Sprintf("%d", s.Version()),
68-
},
69-
}
65+
uniqueOrgIdMatches := make(map[string]struct{})
66+
for _, match := range organizationIdPat.FindAllStringSubmatch(dataStr, -1) {
67+
uniqueOrgIdMatches[match[1]] = struct{}{}
68+
}
69+
if len(uniqueOrgIdMatches) == 0 {
70+
// we only need an org ID to pass into AnalysisInfo
71+
// if we don't find one, we can still verify the key
72+
// we can add a dummy entry here just to make sure a result is returned
73+
uniqueOrgIdMatches[""] = struct{}{}
74+
}
7075

71-
if verify {
72-
client := s.client
73-
if client == nil {
74-
client = defaultClient
76+
for match := range uniqueMatches {
77+
for orgId := range uniqueOrgIdMatches {
78+
s1 := detectors.Result{
79+
DetectorType: detectorspb.DetectorType_Atlassian,
80+
Raw: []byte(match),
81+
ExtraData: map[string]string{
82+
"rotation_guide": "https://howtorotate.com/docs/tutorials/atlassian/",
83+
"version": fmt.Sprintf("%d", s.Version()),
84+
},
7585
}
7686

77-
isVerified, orgResponse, verificationErr := verifyMatch(ctx, client, match)
78-
s1.Verified = isVerified
79-
if orgResponse != nil && len(orgResponse.Data) > 0 {
80-
s1.ExtraData["Organization"] = orgResponse.Data[0].Attributes.Name
87+
if verify {
88+
client := s.client
89+
if client == nil {
90+
client = defaultClient
91+
}
92+
93+
isVerified, orgResponse, verificationErr := verifyMatch(ctx, client, match)
94+
s1.Verified = isVerified
95+
if orgResponse != nil && len(orgResponse.Data) > 0 {
96+
s1.ExtraData["Organization"] = orgResponse.Data[0].Attributes.Name
97+
}
98+
s1.SetVerificationError(verificationErr, match)
99+
if s1.Verified {
100+
s1.AnalysisInfo = map[string]string{
101+
"key": match,
102+
}
103+
if orgId != "" {
104+
s1.AnalysisInfo["organization_id"] = orgId
105+
}
106+
}
81107
}
82-
s1.SetVerificationError(verificationErr, match)
83-
}
84108

85-
results = append(results, s1)
109+
results = append(results, s1)
110+
}
86111
}
87112

88113
return

pkg/detectors/atlassian/v2/atlassian_integration_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func TestAtlassian_FromChunk(t *testing.T) {
136136
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
137137
}
138138
}
139-
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "ExtraData")
139+
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "ExtraData", "primarySecret", "AnalysisInfo")
140140
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
141141
t.Errorf("Atlassian.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
142142
}

pkg/detectors/atlassian/v2/atlassian_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@ package atlassian
22

33
import (
44
"context"
5+
"fmt"
6+
"net/http"
57
"testing"
68

79
"github.com/google/go-cmp/cmp"
10+
"github.com/stretchr/testify/assert"
811
"github.com/stretchr/testify/require"
12+
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
913
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
1014
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
15+
"gopkg.in/h2non/gock.v1"
1116
)
1217

1318
func TestAtlassian_Pattern(t *testing.T) {
@@ -79,3 +84,80 @@ func TestAtlassian_Pattern(t *testing.T) {
7984
})
8085
}
8186
}
87+
88+
// TestAtlassian_AnalysisInfo_KeyAndOrgId tests if both the key and organization id are populated into AnalysisInfo
89+
// given that they are present in the input data chunk
90+
func TestAtlassian_AnalysisInfo_KeyAndOrgId(t *testing.T) {
91+
client := common.SaneHttpClient()
92+
d := Scanner{client: client}
93+
94+
key := "ATCTT3xFfGN0GsZNgOGrQSHSnxiJVi00oHlRicyM0yMNuKCBfw6qOHVcCy4Hm89GnclGb_W-1qAkxqCn5XbuyoX54bNhpK5yFKGFR7ocV6FByvL_P9Sb3tFnbUg3T3I3S_RGCBLMSN7Nsa4GJv8JEJ6bzvDmX-oJ8AnrazMU-zZ5hb-u3t2ERew=366BFE3A"
95+
orgId := "123j4567-e89b-12d3-a456-426614174000"
96+
97+
defer gock.Off()
98+
defer gock.RestoreClient(client)
99+
gock.InterceptClient(client)
100+
gock.New("https://api.atlassian.com").
101+
Get("/admin/v1/orgs").
102+
MatchHeader("Accept", "application/json").
103+
MatchHeader("Authorization", fmt.Sprintf("Bearer %s", key)).
104+
Reply(http.StatusOK).
105+
JSON(map[string]any{
106+
"Data": []map[string]any{},
107+
})
108+
109+
t.Run("key and organization id both present", func(t *testing.T) {
110+
input := fmt.Sprintf(`
111+
[INFO] Sending request to the atlassian API
112+
[DEBUG] Using Key=%s
113+
[DEBUG] Using Organization ID=%s
114+
[INFO] Response received: 200 OK
115+
`, key, orgId)
116+
117+
results, err := d.FromData(context.Background(), true, []byte(input))
118+
require.NoError(t, err)
119+
require.Len(t, results, 1, "mismatch in result count: expected %d, got %d", 1, len(results))
120+
result := results[0]
121+
require.NotNil(t, result.AnalysisInfo, "AnalysisInfo is nil")
122+
123+
assert.Equal(t, key, result.AnalysisInfo["key"], "mismatch in key")
124+
assert.Equal(t, orgId, result.AnalysisInfo["organization_id"], "mismatch in organization_id")
125+
})
126+
}
127+
128+
// TestAtlassian_AnalysisInfo_KeyOnly tests if only key is populated into AnalysisInfo
129+
// given that only the key and no organization_id is present in the input data chunk
130+
func TestAtlassian_AnalysisInfo_KeyOnly(t *testing.T) {
131+
client := common.SaneHttpClient()
132+
d := Scanner{client: client}
133+
134+
key := "ATCTT3xFfGN0GsZNgOGrQSHSnxiJVi00oHlRicyM0yMNuKCBfw6qOHVcCy4Hm89GnclGb_W-1qAkxqCn5XbuyoX54bNhpK5yFKGFR7ocV6FByvL_P9Sb3tFnbUg3T3I3S_RGCBLMSN7Nsa4GJv8JEJ6bzvDmX-oJ8AnrazMU-zZ5hb-u3t2ERew=366BFE3A"
135+
136+
defer gock.Off()
137+
defer gock.RestoreClient(client)
138+
gock.InterceptClient(client)
139+
gock.New("https://api.atlassian.com").
140+
Get("/admin/v1/orgs").
141+
MatchHeader("Accept", "application/json").
142+
MatchHeader("Authorization", fmt.Sprintf("Bearer %s", key)).
143+
Reply(http.StatusOK).
144+
JSON(map[string]any{
145+
"Data": []map[string]any{},
146+
})
147+
t.Run("only key present", func(t *testing.T) {
148+
149+
input := fmt.Sprintf(`
150+
[INFO] Sending request to the atlassian API
151+
[DEBUG] Using Key=%s
152+
[INFO] Response received: 200 OK
153+
`, key)
154+
155+
results, err := d.FromData(context.Background(), true, []byte(input))
156+
require.NoError(t, err)
157+
require.Len(t, results, 1, "mismatch in result count: expected %d, got %d", 1, len(results))
158+
result := results[0]
159+
require.NotNil(t, result.AnalysisInfo, "AnalysisInfo is nil")
160+
161+
assert.Equal(t, key, result.AnalysisInfo["key"], "mismatch in key")
162+
})
163+
}

0 commit comments

Comments
 (0)