This commit is contained in:
Martin Locklear
2025-05-08 14:33:32 -04:00
parent 5f56550804
commit 462ac60b33
6 changed files with 238 additions and 98 deletions
+1
View File
@@ -83,6 +83,7 @@ require (
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.20.5 github.com/prometheus/client_golang v1.20.5
github.com/rabbitmq/amqp091-go v1.10.0 github.com/rabbitmq/amqp091-go v1.10.0
github.com/repeale/fp-go v0.11.1
github.com/sassoftware/go-rpmutils v0.4.0 github.com/sassoftware/go-rpmutils v0.4.0
github.com/schollz/progressbar/v3 v3.17.1 github.com/schollz/progressbar/v3 v3.17.1
github.com/sendgrid/sendgrid-go v3.16.0+incompatible github.com/sendgrid/sendgrid-go v3.16.0+incompatible
+50 -49
View File
@@ -146,7 +146,7 @@ func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, _ .
if err = json.Unmarshal(contents, &env); err != nil { if err = json.Unmarshal(contents, &env); err != nil {
return err return err
} }
s.scanVariableData(ctx, chunksChan, Metadata{EnvironmentID: env.Id, EnvironmentName: env.Name, fromLocal: true, Link: envPath, LocationType: source_metadatapb.PostmanLocationType_ENVIRONMENT_VARIABLE}, env) s.scanVariableData(ctx, chunksChan, Metadata{EnvironmentId: env.Id, EnvironmentName: env.Name, fromLocal: true, Link: envPath, LocationType: source_metadatapb.PostmanLocationType_ENVIRONMENT_VARIABLE}, env)
} }
// Scan local collections // Scan local collections
@@ -235,8 +235,8 @@ func (s *Source) scanLocalWorkspace(ctx context.Context, chunksChan chan *source
s.resetKeywords() s.resetKeywords()
metadata := Metadata{ metadata := Metadata{
WorkspaceUUID: workspace.Id, WorkspaceId: workspace.Id,
fromLocal: true, fromLocal: true,
} }
for _, environment := range workspace.EnvironmentsRaw { for _, environment := range workspace.EnvironmentsRaw {
@@ -246,7 +246,7 @@ func (s *Source) scanLocalWorkspace(ctx context.Context, chunksChan chan *source
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
} }
for _, collection := range workspace.CollectionsRaw { for _, collection := range workspace.CollectionsRaw {
metadata.Link = strings.TrimSuffix(path.Base(filePath), path.Ext(filePath)) + "/collections/" + collection.Info.PostmanID + ".json" metadata.Link = strings.TrimSuffix(path.Base(filePath), path.Ext(filePath)) + "/collections/" + collection.Info.Id + ".json"
s.scanCollection(ctx, chunksChan, metadata, collection) s.scanCollection(ctx, chunksChan, metadata, collection)
} }
} }
@@ -258,7 +258,7 @@ func (s *Source) scanWorkspace(ctx context.Context, chunksChan chan *sources.Chu
// initiate metadata to track the tree structure of postman data // initiate metadata to track the tree structure of postman data
metadata := Metadata{ metadata := Metadata{
WorkspaceUUID: workspace.Id, WorkspaceId: workspace.Id,
WorkspaceName: workspace.Name, WorkspaceName: workspace.Name,
CreatedBy: workspace.CreatedBy, CreatedBy: workspace.CreatedBy,
Type: "workspace", Type: "workspace",
@@ -266,21 +266,21 @@ func (s *Source) scanWorkspace(ctx context.Context, chunksChan chan *sources.Chu
// gather and scan environment variables // gather and scan environment variables
for _, envID := range workspace.Environments { for _, envID := range workspace.Environments {
envVars, err := s.client.GetEnvironmentVariables(ctx, envID.Uid) envVars, err := s.client.GetEnvironmentVariables(ctx, envID.Id)
if err != nil { if err != nil {
ctx.Logger().Error(err, "could not get env variables", "environment_uuid", envID.Uid) ctx.Logger().Error(err, "could not get env variables", "environment_id", envID.Id)
continue continue
} }
if shouldSkip(envID.Uid, s.conn.IncludeEnvironments, s.conn.ExcludeEnvironments) { if shouldSkip(envID.Id, s.conn.IncludeEnvironments, s.conn.ExcludeEnvironments) {
continue continue
} }
metadata.Type = ENVIRONMENT_TYPE metadata.Type = ENVIRONMENT_TYPE
metadata.Link = LINK_BASE_URL + "environments/" + envID.Uid metadata.Link = LINK_BASE_URL + "environments/" + envID.Id
metadata.FullID = envVars.Id metadata.Id = envVars.Id
metadata.EnvironmentID = envID.Uid metadata.EnvironmentId = envID.Id
metadata.EnvironmentName = envVars.Name metadata.EnvironmentName = envVars.Name
ctx.Logger().V(2).Info("scanning environment vars", "environment_uuid", metadata.FullID) ctx.Logger().V(2).Info("scanning environment vars", "environment_id", metadata.Id)
for _, word := range strings.Split(envVars.Name, " ") { for _, word := range strings.Split(envVars.Name, " ") {
s.attemptToAddKeyword(word) s.attemptToAddKeyword(word)
} }
@@ -289,10 +289,10 @@ func (s *Source) scanWorkspace(ctx context.Context, chunksChan chan *sources.Chu
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
metadata.Type = "" metadata.Type = ""
metadata.Link = "" metadata.Link = ""
metadata.FullID = "" metadata.Id = ""
metadata.EnvironmentID = "" metadata.EnvironmentId = ""
metadata.EnvironmentName = "" metadata.EnvironmentName = ""
ctx.Logger().V(2).Info("finished scanning environment vars", "environment_uuid", metadata.FullID) ctx.Logger().V(2).Info("finished scanning environment vars", "environment_id", metadata.Id)
} }
ctx.Logger().V(2).Info("finished scanning environments") ctx.Logger().V(2).Info("finished scanning environments")
@@ -300,10 +300,10 @@ func (s *Source) scanWorkspace(ctx context.Context, chunksChan chan *sources.Chu
// at this point we have all the possible // at this point we have all the possible
// substitutions from Environment variables // substitutions from Environment variables
for _, collectionID := range workspace.Collections { for _, collectionID := range workspace.Collections {
if shouldSkip(collectionID.Uid, s.conn.IncludeCollections, s.conn.ExcludeCollections) { if shouldSkip(collectionID.Id, s.conn.IncludeCollections, s.conn.ExcludeCollections) {
continue continue
} }
collection, err := s.client.GetCollection(ctx, collectionID.Uid) collection, err := s.client.GetCollection(ctx, collectionID.Id)
if err != nil { if err != nil {
// Log and move on, because sometimes the Postman API seems to give us collection IDs // Log and move on, because sometimes the Postman API seems to give us collection IDs
// that we don't have access to, so we don't want to kill the scan because of it. // that we don't have access to, so we don't want to kill the scan because of it.
@@ -318,14 +318,14 @@ func (s *Source) scanWorkspace(ctx context.Context, chunksChan chan *sources.Chu
// scanCollection scans a collection and all its items, folders, and requests. // scanCollection scans a collection and all its items, folders, and requests.
// locally scoped Metadata is updated as we drill down into the collection. // locally scoped Metadata is updated as we drill down into the collection.
func (s *Source) scanCollection(ctx context.Context, chunksChan chan *sources.Chunk, metadata Metadata, collection Collection) { func (s *Source) scanCollection(ctx context.Context, chunksChan chan *sources.Chunk, metadata Metadata, collection Collection) {
ctx.Logger().V(2).Info("starting to scan collection", "collection_name", collection.Info.Name, "collection_uuid", collection.Info.Uid) ctx.Logger().V(2).Info("starting to scan collection", "collection_name", collection.Info.Name, "collection_id", collection.Info.Id)
metadata.CollectionInfo = collection.Info metadata.CollectionInfo = collection.Info
metadata.Type = COLLECTION_TYPE metadata.Type = COLLECTION_TYPE
s.attemptToAddKeyword(collection.Info.Name) s.attemptToAddKeyword(collection.Info.Name)
if !metadata.fromLocal { if !metadata.fromLocal {
metadata.FullID = metadata.CollectionInfo.Uid metadata.Id = metadata.CollectionInfo.Id
metadata.Link = LINK_BASE_URL + COLLECTION_TYPE + "/" + metadata.FullID metadata.Link = LINK_BASE_URL + COLLECTION_TYPE + "/" + metadata.Id
} }
metadata.LocationType = source_metadatapb.PostmanLocationType_COLLECTION_VARIABLE metadata.LocationType = source_metadatapb.PostmanLocationType_COLLECTION_VARIABLE
@@ -361,37 +361,38 @@ func (s *Source) scanItem(ctx context.Context, chunksChan chan *sources.Chunk, c
metadata.FolderName = item.Name metadata.FolderName = item.Name
} }
if item.Uid != "" { if item.Id != "" {
metadata.FullID = item.Uid metadata.Id = item.Id
metadata.Link = LINK_BASE_URL + FOLDER_TYPE + "/" + metadata.FullID metadata.Link = LINK_BASE_URL + FOLDER_TYPE + "/" + metadata.Id
} }
// recurse through the folders // recurse through the folders
for _, subItem := range item.Items { for _, subItem := range item.Items {
s.scanItem(ctx, chunksChan, collection, metadata, subItem, item.Uid) s.scanItem(ctx, chunksChan, collection, metadata, subItem, item.Id)
} }
// The assignment of the folder ID to be the current item UID is due to wanting to assume that your current item is a folder unless you have request data inside of your item. // The assignment of the folder ID to be the current item ID is due to wanting to assume that your current item
// If your current item is a folder, you will want the folder ID to match the UID of the current item. // is a folder unless you have request data inside of your item.
// If your current item is a request, you will want the folder ID to match the UID of the parent folder. // If your current item is a folder, you will want the folder ID to match the ID of the current item.
// If your current item is a request, you will want the folder ID to match the ID of the parent folder.
// If the request is at the root of a collection and has no parent folder, the folder ID will be empty. // If the request is at the root of a collection and has no parent folder, the folder ID will be empty.
metadata.FolderID = item.Uid metadata.FolderId = item.Id
// check if there are any requests in the folder // check if there are any requests in the folder
if item.Request.Method != "" { if item.Request.Method != "" {
metadata.FolderName = strings.Replace(metadata.FolderName, (" > " + item.Name), "", -1) metadata.FolderName = strings.Replace(metadata.FolderName, (" > " + item.Name), "", -1)
metadata.FolderID = parentItemId metadata.FolderId = parentItemId
if metadata.FolderID == "" { if metadata.FolderId == "" {
metadata.FolderName = "" metadata.FolderName = ""
} }
metadata.RequestID = item.Uid metadata.RequestId = item.Id
metadata.RequestName = item.Name metadata.RequestName = item.Name
metadata.Type = REQUEST_TYPE metadata.Type = REQUEST_TYPE
if item.Uid != "" { if item.Id != "" {
// Route to API endpoint // Route to API endpoint
metadata.FullID = item.Uid metadata.Id = item.Id
metadata.Link = LINK_BASE_URL + REQUEST_TYPE + "/" + item.Uid metadata.Link = LINK_BASE_URL + REQUEST_TYPE + "/" + item.Id
} else { } else {
// Route to collection.json // Route to collection.json
metadata.FullID = item.Id metadata.Id = item.Id
} }
s.scanHTTPRequest(ctx, chunksChan, metadata, item.Request) s.scanHTTPRequest(ctx, chunksChan, metadata, item.Request)
} }
@@ -415,7 +416,7 @@ func (s *Source) scanEvent(ctx context.Context, chunksChan chan *sources.Chunk,
// Prep direct links. Ignore updating link if it's a local JSON file // Prep direct links. Ignore updating link if it's a local JSON file
if !metadata.fromLocal { if !metadata.fromLocal {
metadata.Link = LINK_BASE_URL + (strings.Replace(metadata.Type, " > event", "", -1)) + "/" + metadata.FullID metadata.Link = LINK_BASE_URL + (strings.Replace(metadata.Type, " > event", "", -1)) + "/" + metadata.Id
if event.Listen == "prerequest" { if event.Listen == "prerequest" {
metadata.Link += "?tab=pre-request-scripts" metadata.Link += "?tab=pre-request-scripts"
} else { } else {
@@ -624,9 +625,9 @@ func (s *Source) scanRequestBody(ctx context.Context, chunksChan chan *sources.C
} }
func (s *Source) scanHTTPResponse(ctx context.Context, chunksChan chan *sources.Chunk, m Metadata, response Response) { func (s *Source) scanHTTPResponse(ctx context.Context, chunksChan chan *sources.Chunk, m Metadata, response Response) {
if response.Uid != "" { if response.Id != "" {
m.Link = LINK_BASE_URL + "example/" + response.Uid m.Link = LINK_BASE_URL + "example/" + response.Id
m.FullID = response.Uid m.Id = response.Id
} }
originalType := m.Type originalType := m.Type
@@ -664,7 +665,7 @@ func (s *Source) scanHTTPResponse(ctx context.Context, chunksChan chan *sources.
func (s *Source) scanVariableData(ctx context.Context, chunksChan chan *sources.Chunk, m Metadata, variableData VariableData) { func (s *Source) scanVariableData(ctx context.Context, chunksChan chan *sources.Chunk, m Metadata, variableData VariableData) {
if len(variableData.KeyValues) == 0 { if len(variableData.KeyValues) == 0 {
ctx.Logger().V(2).Info("no variables to scan", "type", m.Type, "item_uuid", m.FullID) ctx.Logger().V(2).Info("no variables to scan", "type", m.Type, "item_id", m.Id)
return return
} }
@@ -700,7 +701,7 @@ func (s *Source) scanVariableData(ctx context.Context, chunksChan chan *sources.
func (s *Source) scanData(ctx context.Context, chunksChan chan *sources.Chunk, data string, metadata Metadata) { func (s *Source) scanData(ctx context.Context, chunksChan chan *sources.Chunk, data string, metadata Metadata) {
if data == "" { if data == "" {
ctx.Logger().V(3).Info("Data string is empty", "workspace_id", metadata.WorkspaceUUID) ctx.Logger().V(3).Info("Data string is empty", "workspace_id", metadata.WorkspaceId)
return return
} }
if metadata.FieldType == "" { if metadata.FieldType == "" {
@@ -718,15 +719,15 @@ func (s *Source) scanData(ctx context.Context, chunksChan chan *sources.Chunk, d
Data: &source_metadatapb.MetaData_Postman{ Data: &source_metadatapb.MetaData_Postman{
Postman: &source_metadatapb.Postman{ Postman: &source_metadatapb.Postman{
Link: metadata.Link, Link: metadata.Link,
WorkspaceUuid: metadata.WorkspaceUUID, WorkspaceUuid: metadata.WorkspaceId,
WorkspaceName: metadata.WorkspaceName, WorkspaceName: metadata.WorkspaceName,
CollectionId: metadata.CollectionInfo.Uid, CollectionId: metadata.CollectionInfo.Id,
CollectionName: metadata.CollectionInfo.Name, CollectionName: metadata.CollectionInfo.Name,
EnvironmentId: metadata.EnvironmentID, EnvironmentId: metadata.EnvironmentId,
EnvironmentName: metadata.EnvironmentName, EnvironmentName: metadata.EnvironmentName,
RequestId: metadata.RequestID, RequestId: metadata.RequestId,
RequestName: metadata.RequestName, RequestName: metadata.RequestName,
FolderId: metadata.FolderID, FolderId: metadata.FolderId,
FolderName: metadata.FolderName, FolderName: metadata.FolderName,
FieldType: metadata.FieldType, FieldType: metadata.FieldType,
LocationType: metadata.LocationType, LocationType: metadata.LocationType,
@@ -776,11 +777,11 @@ func unpackWorkspace(workspacePath string) (Workspace, error) {
return workspace, nil return workspace, nil
} }
func shouldSkip(uuid string, include []string, exclude []string) bool { func shouldSkip(object_id string, include []string, exclude []string) bool {
if slices.Contains(exclude, uuid) { if slices.Contains(exclude, object_id) {
return true return true
} }
if len(include) > 0 && !slices.Contains(include, uuid) { if len(include) > 0 && !slices.Contains(include, object_id) {
return true return true
} }
return false return false
+35 -39
View File
@@ -22,25 +22,24 @@ const (
) )
type Workspace struct { type Workspace struct {
Id string `json:"id"` Id string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
Description string `json:"description"` Description string `json:"description"`
Visibility string `json:"visibility"` Visibility string `json:"visibility"`
CreatedBy string `json:"createdBy"` CreatedBy string `json:"createdBy"`
UpdatedBy string `json:"updatedBy"` UpdatedBy string `json:"updatedBy"`
CreatedAt string `json:"createdAt"` CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"` UpdatedAt string `json:"updatedAt"`
Collections []IdNameUid `json:"collections"` Collections []IdName `json:"collections"`
Environments []IdNameUid `json:"environments"` Environments []IdName `json:"environments"`
CollectionsRaw []Collection CollectionsRaw []Collection
EnvironmentsRaw []VariableData EnvironmentsRaw []VariableData
} }
type IdNameUid struct { type IdName struct {
Id string `json:"id"` Id string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Uid string `json:"uid"`
} }
type KeyValue struct { type KeyValue struct {
@@ -53,7 +52,7 @@ type KeyValue struct {
} }
type VariableData struct { type VariableData struct {
Id string `json:"id"` // For globals and envs, this is just the UUID, not the full ID. Id string `json:"id"` // UUID of the folder (but not full ID/UID)
Name string `json:"name"` Name string `json:"name"`
KeyValues []KeyValue `json:"values"` KeyValues []KeyValue `json:"values"`
Owner string `json:"owner"` Owner string `json:"owner"`
@@ -67,16 +66,16 @@ type Environment struct {
} }
type Metadata struct { type Metadata struct {
WorkspaceUUID string WorkspaceId string
WorkspaceName string WorkspaceName string
CreatedBy string CreatedBy string
EnvironmentID string EnvironmentId string
CollectionInfo Info CollectionInfo Info
FolderID string // UUID of the folder (but not full ID) FolderId string // UUID of the folder (but not full ID/UID)
FolderName string // Folder path if the item is nested under one or more folders FolderName string // Folder path if the item is nested under one or more folders
RequestID string // UUID of the request (but not full ID) RequestId string // UUID of the folder (but not full ID/UID)
RequestName string RequestName string
FullID string //full ID of the reference item (created_by + ID) OR just the UUID Id string // Postman designated object ID (not the UID)
Link string //direct link to the folder (could be .json file path) Link string //direct link to the folder (could be .json file path)
Type string //folder, request, etc. Type string //folder, request, etc.
EnvironmentName string EnvironmentName string
@@ -94,12 +93,11 @@ type Collection struct {
} }
type Info struct { type Info struct {
PostmanID string `json:"_postman_id"` // This is a UUID. Needs createdBy ID prefix to be used with API. Id string `json:"id"` // This is a UUID, but not a UID
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
Schema string `json:"schema"` Schema string `json:"schema"`
UpdatedAt time.Time `json:"updatedAt"` UpdatedAt time.Time `json:"updatedAt"`
Uid string `json:"uid"` //Need to use this to get the collection via API
} }
type Item struct { type Item struct {
@@ -112,7 +110,6 @@ type Item struct {
Request Request `json:"request,omitempty"` Request Request `json:"request,omitempty"`
Response []Response `json:"response,omitempty"` Response []Response `json:"response,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
Uid string `json:"uid,omitempty"` //Need to use this to get the collection via API. The UID is a concatenation of the ID and the user ID of whoever created the item.
} }
type Auth struct { type Auth struct {
@@ -180,7 +177,6 @@ type Response struct {
HeaderKeyValue []KeyValue HeaderKeyValue []KeyValue
HeaderString []string HeaderString []string
Body string `json:"body,omitempty"` Body string `json:"body,omitempty"`
Uid string `json:"uid,omitempty"`
} }
// A Client manages communication with the Postman API. // A Client manages communication with the Postman API.
@@ -308,64 +304,64 @@ func (c *Client) EnumerateWorkspaces(ctx context.Context) ([]Workspace, error) {
} }
// GetWorkspace returns the workspace for a given workspace // GetWorkspace returns the workspace for a given workspace
func (c *Client) GetWorkspace(ctx context.Context, workspaceUUID string) (Workspace, error) { func (c *Client) GetWorkspace(ctx context.Context, workspaceId string) (Workspace, error) {
ctx.Logger().V(2).Info("getting workspace", "workspace", workspaceUUID) ctx.Logger().V(2).Info("getting workspace", "workspace", workspaceId)
obj := struct { obj := struct {
Workspace Workspace `json:"workspace"` Workspace Workspace `json:"workspace"`
}{} }{}
url := fmt.Sprintf(WORKSPACE_URL, workspaceUUID) url := fmt.Sprintf(WORKSPACE_URL, workspaceId)
if err := c.WorkspaceAndCollectionRateLimiter.Wait(ctx); err != nil { if err := c.WorkspaceAndCollectionRateLimiter.Wait(ctx); err != nil {
return Workspace{}, fmt.Errorf("could not wait for rate limiter during workspace getting: %w", err) return Workspace{}, fmt.Errorf("could not wait for rate limiter during workspace getting: %w", err)
} }
body, err := c.getPostmanResponseBodyBytes(ctx, url, nil) body, err := c.getPostmanResponseBodyBytes(ctx, url, nil)
if err != nil { if err != nil {
return Workspace{}, fmt.Errorf("could not get postman workspace (%s) response bytes: %w", workspaceUUID, err) return Workspace{}, fmt.Errorf("could not get postman workspace (%s) response bytes: %w", workspaceId, err)
} }
if err := json.Unmarshal([]byte(body), &obj); err != nil { if err := json.Unmarshal([]byte(body), &obj); err != nil {
return Workspace{}, fmt.Errorf("could not unmarshal workspace JSON for workspace (%s): %w", workspaceUUID, err) return Workspace{}, fmt.Errorf("could not unmarshal workspace JSON for workspace (%s): %w", workspaceId, err)
} }
return obj.Workspace, nil return obj.Workspace, nil
} }
// GetEnvironmentVariables returns the environment variables for a given environment // GetEnvironmentVariables returns the environment variables for a given environment
func (c *Client) GetEnvironmentVariables(ctx context.Context, environment_uuid string) (VariableData, error) { func (c *Client) GetEnvironmentVariables(ctx context.Context, environmentId string) (VariableData, error) {
obj := struct { obj := struct {
VariableData VariableData `json:"environment"` VariableData VariableData `json:"environment"`
}{} }{}
url := fmt.Sprintf(ENVIRONMENTS_URL, environment_uuid) url := fmt.Sprintf(ENVIRONMENTS_URL, environmentId)
if err := c.GeneralRateLimiter.Wait(ctx); err != nil { if err := c.GeneralRateLimiter.Wait(ctx); err != nil {
return VariableData{}, fmt.Errorf("could not wait for rate limiter during environment variable getting: %w", err) return VariableData{}, fmt.Errorf("could not wait for rate limiter during environment variable getting: %w", err)
} }
body, err := c.getPostmanResponseBodyBytes(ctx, url, nil) body, err := c.getPostmanResponseBodyBytes(ctx, url, nil)
if err != nil { if err != nil {
return VariableData{}, fmt.Errorf("could not get postman environment (%s) response bytes: %w", environment_uuid, err) return VariableData{}, fmt.Errorf("could not get postman environment (%s) response bytes: %w", environmentId, err)
} }
if err := json.Unmarshal([]byte(body), &obj); err != nil { if err := json.Unmarshal([]byte(body), &obj); err != nil {
return VariableData{}, fmt.Errorf("could not unmarshal env variables JSON for environment (%s): %w", environment_uuid, err) return VariableData{}, fmt.Errorf("could not unmarshal env variables JSON for environment (%s): %w", environmentId, err)
} }
return obj.VariableData, nil return obj.VariableData, nil
} }
// GetCollection returns the collection for a given collection // GetCollection returns the collection for a given collection
func (c *Client) GetCollection(ctx context.Context, collection_uuid string) (Collection, error) { func (c *Client) GetCollection(ctx context.Context, collectionId string) (Collection, error) {
obj := struct { obj := struct {
Collection Collection `json:"collection"` Collection Collection `json:"collection"`
}{} }{}
url := fmt.Sprintf(COLLECTIONS_URL, collection_uuid) url := fmt.Sprintf(COLLECTIONS_URL, collectionId)
if err := c.WorkspaceAndCollectionRateLimiter.Wait(ctx); err != nil { if err := c.WorkspaceAndCollectionRateLimiter.Wait(ctx); err != nil {
return Collection{}, fmt.Errorf("could not wait for rate limiter during collection getting: %w", err) return Collection{}, fmt.Errorf("could not wait for rate limiter during collection getting: %w", err)
} }
body, err := c.getPostmanResponseBodyBytes(ctx, url, nil) body, err := c.getPostmanResponseBodyBytes(ctx, url, nil)
if err != nil { if err != nil {
return Collection{}, fmt.Errorf("could not get postman collection (%s) response bytes: %w", collection_uuid, err) return Collection{}, fmt.Errorf("could not get postman collection (%s) response bytes: %w", collectionId, err)
} }
if err := json.Unmarshal([]byte(body), &obj); err != nil { if err := json.Unmarshal([]byte(body), &obj); err != nil {
return Collection{}, fmt.Errorf("could not unmarshal JSON for collection (%s): %w", collection_uuid, err) return Collection{}, fmt.Errorf("could not unmarshal JSON for collection (%s): %w", collectionId, err)
} }
// Loop used to deal with seeing whether a request/response header is a string or a key value pair // Loop used to deal with seeing whether a request/response header is a string or a key value pair
@@ -374,7 +370,7 @@ func (c *Client) GetCollection(ctx context.Context, collection_uuid string) (Col
if err := json.Unmarshal(obj.Collection.Items[i].Request.HeaderRaw, &obj.Collection.Items[i].Request.HeaderKeyValue); err == nil { if err := json.Unmarshal(obj.Collection.Items[i].Request.HeaderRaw, &obj.Collection.Items[i].Request.HeaderKeyValue); err == nil {
} else if err := json.Unmarshal(obj.Collection.Items[i].Request.HeaderRaw, &obj.Collection.Items[i].Request.HeaderString); err == nil { } else if err := json.Unmarshal(obj.Collection.Items[i].Request.HeaderRaw, &obj.Collection.Items[i].Request.HeaderString); err == nil {
} else { } else {
return Collection{}, fmt.Errorf("could not unmarshal request header JSON for collection (%s): %w", collection_uuid, err) return Collection{}, fmt.Errorf("could not unmarshal request header JSON for collection (%s): %w", collectionId, err)
} }
} }
@@ -382,13 +378,13 @@ func (c *Client) GetCollection(ctx context.Context, collection_uuid string) (Col
if err := json.Unmarshal(obj.Collection.Items[i].Response[j].OriginalRequest.HeaderRaw, &obj.Collection.Items[i].Response[j].OriginalRequest.HeaderKeyValue); err == nil { if err := json.Unmarshal(obj.Collection.Items[i].Response[j].OriginalRequest.HeaderRaw, &obj.Collection.Items[i].Response[j].OriginalRequest.HeaderKeyValue); err == nil {
} else if err := json.Unmarshal(obj.Collection.Items[i].Response[j].OriginalRequest.HeaderRaw, &obj.Collection.Items[i].Response[j].OriginalRequest.HeaderString); err == nil { } else if err := json.Unmarshal(obj.Collection.Items[i].Response[j].OriginalRequest.HeaderRaw, &obj.Collection.Items[i].Response[j].OriginalRequest.HeaderString); err == nil {
} else { } else {
return Collection{}, fmt.Errorf("could not unmarshal original request header in response JSON for collection (%s): %w", collection_uuid, err) return Collection{}, fmt.Errorf("could not unmarshal original request header in response JSON for collection (%s): %w", collectionId, err)
} }
if err := json.Unmarshal(obj.Collection.Items[i].Response[j].HeaderRaw, &obj.Collection.Items[i].Response[j].HeaderKeyValue); err == nil { if err := json.Unmarshal(obj.Collection.Items[i].Response[j].HeaderRaw, &obj.Collection.Items[i].Response[j].HeaderKeyValue); err == nil {
} else if err := json.Unmarshal(obj.Collection.Items[i].Response[j].HeaderRaw, &obj.Collection.Items[i].Response[j].HeaderString); err == nil { } else if err := json.Unmarshal(obj.Collection.Items[i].Response[j].HeaderRaw, &obj.Collection.Items[i].Response[j].HeaderString); err == nil {
} else { } else {
return Collection{}, fmt.Errorf("could not unmarshal response header JSON for collection (%s): %w", collection_uuid, err) return Collection{}, fmt.Errorf("could not unmarshal response header JSON for collection (%s): %w", collectionId, err)
} }
} }
} }
+150 -8
View File
@@ -2,9 +2,12 @@ package postman
import ( import (
"fmt" "fmt"
"github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"math/rand"
"reflect" "reflect"
"sort" "sort"
"strconv"
"strings" "strings"
"testing" "testing"
"time" "time"
@@ -12,6 +15,7 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/context" "github.com/trufflesecurity/trufflehog/v3/pkg/context"
"gopkg.in/h2non/gock.v1" "gopkg.in/h2non/gock.v1"
"github.com/repeale/fp-go"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources" "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
"google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/anypb"
@@ -71,8 +75,8 @@ func TestSource_ScanCollection(t *testing.T) {
name: "GET request with URL", name: "GET request with URL",
collection: Collection{ collection: Collection{
Info: Info{ Info: Info{
PostmanID: "col1", Id: "col1",
Name: "Test Collection", Name: "Test Collection",
}, },
Items: []Item{ Items: []Item{
{ {
@@ -97,8 +101,8 @@ func TestSource_ScanCollection(t *testing.T) {
name: "POST request with URL and auth", name: "POST request with URL and auth",
collection: Collection{ collection: Collection{
Info: Info{ Info: Info{
PostmanID: "col2", Id: "col2",
Name: "Test Collection", Name: "Test Collection",
}, },
Items: []Item{ Items: []Item{
{ {
@@ -180,8 +184,8 @@ func TestSource_ScanVariableData(t *testing.T) {
name: "Single variable", name: "Single variable",
metadata: Metadata{ metadata: Metadata{
CollectionInfo: Info{ CollectionInfo: Info{
PostmanID: "col1", Id: "col1",
Name: "Test Collection", Name: "Test Collection",
}, },
}, },
variableData: VariableData{ variableData: VariableData{
@@ -200,8 +204,8 @@ func TestSource_ScanVariableData(t *testing.T) {
name: "Multiple variables", name: "Multiple variables",
metadata: Metadata{ metadata: Metadata{
CollectionInfo: Info{ CollectionInfo: Info{
PostmanID: "col2", Id: "col2",
Name: "Test Collection", Name: "Test Collection",
}, },
}, },
variableData: VariableData{ variableData: VariableData{
@@ -686,3 +690,141 @@ func TestSource_HeadersScanning(t *testing.T) {
t.Logf("Generated %d chunks from the mock data", chunksReceived) t.Logf("Generated %d chunks from the mock data", chunksReceived)
} }
} }
func makePostmanUid[A, B any](userId A, objectId B) string {
return fmt.Sprint(userId, '-', objectId)
}
func Test_WorkspaceListUnmarshalling(t *testing.T) {
// This test is designed to cover the unmarshalling of the response to a request
// like: curl --location 'https://api.getpostman.com/workspaces'
defer gock.Off()
aUserId := strconv.Itoa(10000000 + rand.Intn(99999999-10000000))
myWorkspaceId := uuid.New().String()
myWorkspaceTestCollectionId := uuid.New().String()
myWorkspaceTestCollectionUid := makePostmanUid(aUserId, myWorkspaceTestCollectionId)
myWorkspaceTestEnvironemntId := uuid.New().String()
myWorkspaceTestEnvironemntUid := makePostmanUid(aUserId, myWorkspaceTestEnvironemntId)
privateWorkspaceId := uuid.New().String()
teamWorkspaceId := uuid.New().String()
publicWorkspaceId := uuid.New().String()
partnerWorkspaceId := uuid.New().String()
// Set up some responses
successfulWorkspaceListResponseStr := `
{
"workspaces": [
{
"id": "` + myWorkspaceId + `",
"name": "My Workspace",
"createdBy": "` + aUserId + `",
"type": "personal",
"visibility": "personal"
},
{
"id": "` + privateWorkspaceId + `",
"name": "Private Workspace",
"createdBy": "` + aUserId + `",
"type": "team",
"visibility": "private"
},
{
"id": "` + teamWorkspaceId + `",
"name": "Team Workspace",
"createdBy": "` + aUserId + `",
"type": "team",
"visibility": "team"
},
{
"id": "` + publicWorkspaceId + `",
"name": "Public Workspace",
"createdBy": "` + aUserId + `",
"type": "team",
"visibility": "public"
},
{
"id": "` + partnerWorkspaceId + `",
"name": "Partner Workspace",
"createdBy": "` + aUserId + `",
"type": "team",
"visibility": "partner"
}
]
}
`
gock.New("https://api.getpostman.com").
Get("/workspaces").
Reply(200).
BodyString(successfulWorkspaceListResponseStr)
successfulMyWorkspaceDetailsResponseStr := `
{
"workspace": {
"id": "` + myWorkspaceId + `",
"name": "Partner Workspace",
"type": "team",
"description": "This is a partner workspace.",
"visibility": "partner",
"createdBy": "` + aUserId + `",
"updatedBy": "` + aUserId + `",
"createdAt": "2022-07-06T16:18:32.000Z",
"updatedAt": "2022-07-06T20:55:13.000Z",
"collections": [
{
"id": "` + myWorkspaceTestCollectionId + `",
"name": "Test Collection",
"uid": "` + myWorkspaceTestCollectionUid + `"
}
],
"environments": [
{
"id": "` + myWorkspaceTestEnvironemntId + `",
"name": "Test Environment",
"uid": "` + myWorkspaceTestEnvironemntUid + `"
}
],
"mocks": [],
"monitors": [],
"apis": [],
"scim": {
"createdBy": "405775fe15ed41872a8eea4c8aa2b38cda9749812cc55c99",
"updatedBy": "405775fe15ed41872a8eea4c8aa2b38cda9749812cc55c99"
}
}
}
`
gock.New("https://api.getpostman.com").
Get("/workspaces/" + myWorkspaceId).
Reply(200).
BodyString(successfulMyWorkspaceDetailsResponseStr)
// Generice setup that we have to do every time
ctx := context.Background()
s, conn := createTestSource(&sourcespb.Postman{
Credential: &sourcespb.Postman_Token{
Token: "super-secret-token",
},
})
s.Init(ctx, "test - postman", 0, 1, false, conn, 1)
gock.InterceptClient(s.client.HTTPClient)
defer gock.RestoreClient(s.client.HTTPClient)
// Kick off the thing. At this point we expect calls to the following:
// 1) https://api.getpostman.com/workspaces
// - to get the initial list of workspaces
// 2) https://api.getpostman.com/workspaces/{workspace_id}
// - for _each_ of the returned workspaces, to get their full information
workspaces, _ := s.client.EnumerateWorkspaces(ctx)
// First we check that the returned list of workspaces looks exactly like we want it to
actualWorkspaceIds := fp.Map(func(w Workspace) string { return w.Id })(workspaces)
expectedWorkspaceIds := []string{myWorkspaceId, privateWorkspaceId, publicWorkspaceId, partnerWorkspaceId, teamWorkspaceId}
assert.ElementsMatch(t, actualWorkspaceIds, expectedWorkspaceIds)
// Now we check that the appropriate calls were made
fmt.Printf("%v", workspaces)
}
+1 -1
View File
@@ -67,7 +67,7 @@ func (s *Source) buildSubstitution(data string, metadata Metadata, combos *map[s
matches := removeDuplicateStr(subRe.FindAllString(data, -1)) matches := removeDuplicateStr(subRe.FindAllString(data, -1))
for _, match := range matches { for _, match := range matches {
for _, slice := range s.sub.variables[strings.Trim(match, "{}")] { for _, slice := range s.sub.variables[strings.Trim(match, "{}")] {
if slice.Metadata.CollectionInfo.PostmanID != "" && slice.Metadata.CollectionInfo.PostmanID != metadata.CollectionInfo.PostmanID { if slice.Metadata.CollectionInfo.Id != "" && slice.Metadata.CollectionInfo.Id != metadata.CollectionInfo.Id {
continue continue
} }
+1 -1
View File
@@ -17,7 +17,7 @@ func TestNewSubstitution(t *testing.T) {
func TestSubstitution_Add(t *testing.T) { func TestSubstitution_Add(t *testing.T) {
sub := NewSubstitution() sub := NewSubstitution()
metadata := Metadata{ metadata := Metadata{
CollectionInfo: Info{PostmanID: "col1"}, CollectionInfo: Info{Id: "col1"},
} }
sub.add(metadata, "key1", "value1") sub.add(metadata, "key1", "value1")
sub.add(metadata, "key1", "value2") sub.add(metadata, "key1", "value2")