Skip to content

Commit

Permalink
[analyze] Add analyzer interface for Shopify (#3226)
Browse files Browse the repository at this point in the history
* implement analyzer interface for shopify

* fixed shopify analyzer according to new code and generated permissions

* shopify analyzer test added

* [chore]
- key validations
- linked analyzer with detectors

* [chore]
- moved redundant initialize to global.

* [chore]
moved expected output of test in json file to neat the code.

* [Fixes]
- Fixed permission and category resource issue in shopify analyzer
- corrected test for shopify analyzer

---------

Co-authored-by: Abdul Basit <abasit@folio3.com>
  • Loading branch information
abmussani and abasit-folio3 committed Sep 12, 2024
1 parent 57e5812 commit dc9c9a3
Show file tree
Hide file tree
Showing 7 changed files with 441 additions and 3 deletions.
177 changes: 177 additions & 0 deletions pkg/analyzer/analyzers/shopify/expected_output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
{
"AnalyzerType": 15,
"Bindings": [
{
"Resource": {
"Name": "Analytics",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/Analytics",
"Type": "category",
"Metadata": null,
"Parent": {
"Name": "My Store",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
"Type": "shop",
"Metadata": {
"created_at": "2024-08-16T17:16:17+05:00"
},
"Parent": null
}
},
"Permission": {
"Value": "read",
"Parent": null
}
},
{
"Resource": {
"Name": "Applications",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/Applications",
"Type": "category",
"Metadata": null,
"Parent": {
"Name": "My Store",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
"Type": "shop",
"Metadata": {
"created_at": "2024-08-16T17:16:17+05:00"
},
"Parent": null
}
},
"Permission": {
"Value": "read",
"Parent": null
}
},
{
"Resource": {
"Name": "Assigned fulfillment orders",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/Assigned fulfillment orders",
"Type": "category",
"Metadata": null,
"Parent": {
"Name": "My Store",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
"Type": "shop",
"Metadata": {
"created_at": "2024-08-16T17:16:17+05:00"
},
"Parent": null
}
},
"Permission": {
"Value": "full_access",
"Parent": null
}
},
{
"Resource": {
"Name": "Customers",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/Customers",
"Type": "category",
"Metadata": null,
"Parent": {
"Name": "My Store",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
"Type": "shop",
"Metadata": {
"created_at": "2024-08-16T17:16:17+05:00"
},
"Parent": null
}
},
"Permission": {
"Value": "full_access",
"Parent": null
}
},
{
"Resource": {
"Name": "Discovery",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/Discovery",
"Type": "category",
"Metadata": null,
"Parent": {
"Name": "My Store",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
"Type": "shop",
"Metadata": {
"created_at": "2024-08-16T17:16:17+05:00"
},
"Parent": null
}
},
"Permission": {
"Value": "full_access",
"Parent": null
}
},
{
"Resource": {
"Name": "Merchant-managed fulfillment orders",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/Merchant-managed fulfillment orders",
"Type": "category",
"Metadata": null,
"Parent": {
"Name": "My Store",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
"Type": "shop",
"Metadata": {
"created_at": "2024-08-16T17:16:17+05:00"
},
"Parent": null
}
},
"Permission": {
"Value": "full_access",
"Parent": null
}
},
{
"Resource": {
"Name": "Reports",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/Reports",
"Type": "category",
"Metadata": null,
"Parent": {
"Name": "My Store",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
"Type": "shop",
"Metadata": {
"created_at": "2024-08-16T17:16:17+05:00"
},
"Parent": null
}
},
"Permission": {
"Value": "full_access",
"Parent": null
}
},
{
"Resource": {
"Name": "cart_transforms",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/cart_transforms",
"Type": "category",
"Metadata": null,
"Parent": {
"Name": "My Store",
"FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
"Type": "shop",
"Metadata": {
"created_at": "2024-08-16T17:16:17+05:00"
},
"Parent": null
}
},
"Permission": {
"Value": "full_access",
"Parent": null
}
}
],
"UnboundedResources": null,
"Metadata": {
"status_code": 200
}
}
71 changes: 71 additions & 0 deletions pkg/analyzer/analyzers/shopify/permissions.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions pkg/analyzer/analyzers/shopify/permissions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
permissions:
- read
- write
- full_access
99 changes: 96 additions & 3 deletions pkg/analyzer/analyzers/shopify/shopify.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
//go:generate generate_permissions permissions.yaml permissions.go shopify

package shopify

import (
_ "embed"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
Expand All @@ -12,8 +15,100 @@ import (
"github.com/jedib0t/go-pretty/table"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/pb/analyzerpb"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)

var _ analyzers.Analyzer = (*Analyzer)(nil)

type Analyzer struct {
Cfg *config.Config
}

var (
// order the categories
categoryOrder = []string{"Analytics", "Applications", "Assigned fulfillment orders", "Browsing behavior", "Custom pixels", "Customers", "Discounts", "Discovery", "Draft orders", "Files", "Fulfillment services", "Gift cards", "Inventory", "Legal policies", "Locations", "Marketing events", "Merchant-managed fulfillment orders", "Metaobject definitions", "Metaobject entries", "Online Store navigation", "Online Store pages", "Order editing", "Orders", "Packing slip management", "Payment customizations", "Payment terms", "Pixels", "Price rules", "Product feeds", "Product listings", "Products", "Publications", "Purchase options", "Reports", "Resource feedback", "Returns", "Sales channels", "Script tags", "Shipping", "Shop locales", "Shopify Markets", "Shopify Payments accounts", "Shopify Payments bank accounts", "Shopify Payments disputes", "Shopify Payments payouts", "Store content", "Store credit account transactions", "Store credit accounts", "Themes", "Third-party fulfillment orders", "Translations", "all_cart_transforms", "all_checkout_completion_target_customizations", "cart_transforms", "cash_tracking", "companies", "custom_fulfillment_services", "customer_data_erasure", "customer_merge", "delivery_customizations", "delivery_option_generators", "discounts_allocator_functions", "fulfillment_constraint_rules", "gates", "order_submission_rules", "privacy_settings", "shopify_payments_provider_accounts_sensitive", "validations"}
)

func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Shopify }

func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
key, ok := credInfo["key"]
if !ok {
return nil, errors.New("key not found in credentialInfo")
}

storeUrl, ok := credInfo["store_url"]
if !ok {
return nil, errors.New("store_url not found in credentialInfo")
}

info, err := AnalyzePermissions(a.Cfg, key, storeUrl)
if err != nil {
return nil, err
}
return secretInfoToAnalyzerResult(info), nil
}

func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
if info == nil {
return nil
}
result := analyzers.AnalyzerResult{
AnalyzerType: analyzerpb.AnalyzerType_Shopify,
Metadata: map[string]any{
"status_code": info.StatusCode,
},
}

resource := &analyzers.Resource{
Name: info.ShopInfo.Shop.Name,
FullyQualifiedName: info.ShopInfo.Shop.Domain + "/" + info.ShopInfo.Shop.Email,
Type: "shop",
Metadata: map[string]any{
"created_at": info.ShopInfo.Shop.CreatedAt,
},
Parent: nil,
}
result.Bindings = make([]analyzers.Binding, 0)

for _, category := range categoryOrder {
if val, ok := info.Scopes[category]; ok {
cateogryResource := &analyzers.Resource{
Name: category,
FullyQualifiedName: resource.FullyQualifiedName + "/" + category, // shop.domain/shop.email/category
Type: "category",
Parent: resource,
}

if sliceContains(val.Scopes, "Read") && sliceContains(val.Scopes, "Write") {
result.Bindings = append(result.Bindings, analyzers.Binding{
Resource: *cateogryResource,
Permission: analyzers.Permission{
Value: PermissionStrings[FullAccess],
},
})
continue
}

for _, scope := range val.Scopes {
lowerScope := strings.ToLower(scope)
if _, ok := StringToPermission[lowerScope]; !ok { // skip unknown scopes/permission
continue
}
result.Bindings = append(result.Bindings, analyzers.Binding{
Resource: *cateogryResource,
Permission: analyzers.Permission{
Value: lowerScope,
},
})
}
}
}

return &result
}

//go:embed scopes.json
var scopesConfig []byte

Expand Down Expand Up @@ -90,6 +185,7 @@ func determineScopes(data ScopeDataJSON, input string) map[string]OutputScopes {

type ShopInfoJSON struct {
Shop struct {
Domain string `json:"domain"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt string `json:"created_at"`
Expand Down Expand Up @@ -224,9 +320,6 @@ func printAccessScopes(accessScopes map[string]OutputScopes) {
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"Scope", "Description", "Access"})

// order the categories
categoryOrder := []string{"Analytics", "Applications", "Assigned fulfillment orders", "Browsing behavior", "Custom pixels", "Customers", "Discounts", "Discovery", "Draft orders", "Files", "Fulfillment services", "Gift cards", "Inventory", "Legal policies", "Locations", "Marketing events", "Merchant-managed fulfillment orders", "Metaobject definitions", "Metaobject entries", "Online Store navigation", "Online Store pages", "Order editing", "Orders", "Packing slip management", "Payment customizations", "Payment terms", "Pixels", "Price rules", "Product feeds", "Product listings", "Products", "Publications", "Purchase options", "Reports", "Resource feedback", "Returns", "Sales channels", "Script tags", "Shipping", "Shop locales", "Shopify Markets", "Shopify Payments accounts", "Shopify Payments bank accounts", "Shopify Payments disputes", "Shopify Payments payouts", "Store content", "Store credit account transactions", "Store credit accounts", "Themes", "Third-party fulfillment orders", "Translations", "all_cart_transforms", "all_checkout_completion_target_customizations", "cart_transforms", "cash_tracking", "companies", "custom_fulfillment_services", "customer_data_erasure", "customer_merge", "delivery_customizations", "delivery_option_generators", "discounts_allocator_functions", "fulfillment_constraint_rules", "gates", "order_submission_rules", "privacy_settings", "shopify_payments_provider_accounts_sensitive", "validations"}

for _, category := range categoryOrder {
if val, ok := accessScopes[category]; ok {
t.AppendRow([]interface{}{color.GreenString(category), color.GreenString(val.Description), color.GreenString(val.PrintScopes())})
Expand Down
Loading

0 comments on commit dc9c9a3

Please sign in to comment.