[analyze] Add Analyzer for Asana (#3139)

* impelmented analyzer interface with data models for Asana

* add unit test for asana analyzer

* link asana detector with analyzer

* added permission for asana
linked detector with only positive cases.

* to make test cleaner moved want json in external file.
Moreover without sorting test will not be able to compare.

* use general functions to avoid code duplication.
optimize app permission making logic.

* [fix] assigned bindings to results.

---------

Co-authored-by: Abdul Basit <abasit@folio3.com>
This commit is contained in:
Abdul Basit 2024-09-07 00:37:45 +05:00 committed by GitHub
parent f235b8a442
commit 4cab071032
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 465 additions and 7 deletions

View file

@ -1,9 +1,11 @@
//go:generate generate_permissions permissions.yaml permissions.go asana
package asana
// ToDo: Add OAuth token support.
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
@ -25,19 +27,64 @@ type Analyzer struct {
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Asana }
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
_, err := AnalyzePermissions(a.Cfg, credInfo["key"])
key, ok := credInfo["key"]
if !ok {
return nil, errors.New("key not found in credInfo")
}
info, err := AnalyzePermissions(a.Cfg, key)
if err != nil {
return nil, err
}
return nil, fmt.Errorf("not implemented")
return secretInfoToAnalyzerResult(info), nil
}
type MeJSON struct {
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
if info == nil {
return nil
}
result := analyzers.AnalyzerResult{}
// resoures/permission setup
permissions := allPermissions()
userResource := analyzers.Resource{
Name: info.Data.Name,
FullyQualifiedName: info.Data.ID,
Type: "user",
Metadata: map[string]any{
"email": info.Data.Email,
"type": info.Data.Type,
},
}
// bindings to all permissions to resources
bindings := analyzers.BindAllPermissions(userResource, permissions...)
result.Bindings = append(result.Bindings, bindings...)
// unbounded resources
result.UnboundedResources = make([]analyzers.Resource, 0, len(info.Data.Workspaces))
for _, workspace := range info.Data.Workspaces {
resource := analyzers.Resource{
Name: workspace.Name,
FullyQualifiedName: workspace.ID,
Type: "workspace",
}
result.UnboundedResources = append(result.UnboundedResources, resource)
}
return &result
}
type SecretInfo struct {
Data struct {
ID string `json:"gid"`
Email string `json:"email"`
Name string `json:"name"`
Type string `json:"resource_type"`
Workspaces []struct {
ID string `json:"gid"`
Name string `json:"name"`
} `json:"workspaces"`
} `json:"data"`
@ -52,8 +99,8 @@ func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
printMetadata(me)
}
func AnalyzePermissions(cfg *config.Config, key string) (*MeJSON, error) {
var me MeJSON
func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
var me SecretInfo
client := analyzers.NewAnalyzeClient(cfg)
req, err := http.NewRequest("GET", "https://app.asana.com/api/1.0/users/me", nil)
@ -84,7 +131,7 @@ func AnalyzePermissions(cfg *config.Config, key string) (*MeJSON, error) {
return &me, nil
}
func printMetadata(me *MeJSON) {
func printMetadata(me *SecretInfo) {
color.Green("[!] Valid Asana API Key\n\n")
color.Yellow("[i] User Information")
color.Yellow(" Name: %s", me.Data.Name)
@ -102,3 +149,13 @@ func printMetadata(me *MeJSON) {
}
t.Render()
}
func allPermissions() []analyzers.Permission {
permissions := make([]analyzers.Permission, 0, len(PermissionStrings))
for _, permission := range PermissionStrings {
permissions = append(permissions, analyzers.Permission{
Value: permission,
})
}
return permissions
}

View file

@ -0,0 +1,100 @@
package asana
import (
_ "embed"
"encoding/json"
"sort"
"testing"
"time"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
//go:embed expected_output.json
var expectedOutput []byte
func TestAnalyzer_Analyze(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
tests := []struct {
name string
key string
want string // JSON string
wantErr bool
}{
{
name: "valid Asana OAUTH Token",
key: testSecrets.MustGetField("ASANAOAUTH_TOKEN"),
want: string(expectedOutput),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := Analyzer{Cfg: &config.Config{}}
got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
if (err != nil) != tt.wantErr {
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
return
}
// bindings need to be in the same order to be comparable
sortBindings(got.Bindings)
// Marshal the actual result to JSON
gotJSON, err := json.Marshal(got)
if err != nil {
t.Fatalf("could not marshal got to JSON: %s", err)
}
// Parse the expected JSON string
var wantObj analyzers.AnalyzerResult
if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
t.Fatalf("could not unmarshal want JSON string: %s", err)
}
// bindings need to be in the same order to be comparable
sortBindings(wantObj.Bindings)
// Marshal the expected result to JSON (to normalize)
wantJSON, err := json.Marshal(wantObj)
if err != nil {
t.Fatalf("could not marshal want to JSON: %s", err)
}
// Compare the JSON strings
if string(gotJSON) != string(wantJSON) {
// Pretty-print both JSON strings for easier comparison
var gotIndented, wantIndented []byte
gotIndented, err = json.MarshalIndent(got, "", " ")
if err != nil {
t.Fatalf("could not marshal got to indented JSON: %s", err)
}
wantIndented, err = json.MarshalIndent(wantObj, "", " ")
if err != nil {
t.Fatalf("could not marshal want to indented JSON: %s", err)
}
t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
}
})
}
}
// Helper function to sort bindings
func sortBindings(bindings []analyzers.Binding) {
sort.SliceStable(bindings, func(i, j int) bool {
if bindings[i].Resource.Name == bindings[j].Resource.Name {
return bindings[i].Permission.Value < bindings[j].Permission.Value
}
return bindings[i].Resource.Name < bindings[j].Resource.Name
})
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,256 @@
// Code generated by go generate; DO NOT EDIT.
package asana
import "errors"
type Permission int
const (
Invalid Permission = iota
AllocationsRead Permission = iota
AllocationsWrite Permission = iota
AttachmentsRead Permission = iota
AttachmentsWrite Permission = iota
AutditLogsRead Permission = iota
AutditLogsWrite Permission = iota
CustomFieldsRead Permission = iota
CustomFieldsWrite Permission = iota
CustomFieldSettingsRead Permission = iota
CustomFieldSettingsWrite Permission = iota
BatchApiRead Permission = iota
BatchApiWrite Permission = iota
EventsRead Permission = iota
EventsWrite Permission = iota
GoalsRead Permission = iota
GoalsWrite Permission = iota
JobsRead Permission = iota
JobsWrite Permission = iota
PortfoliosRead Permission = iota
PortfoliosWrite Permission = iota
ProjectsRead Permission = iota
ProjectsWrite Permission = iota
ProjectMembershipsRead Permission = iota
ProjectMembershipsWrite Permission = iota
SectionsRead Permission = iota
SectionsWrite Permission = iota
TagsRead Permission = iota
TagsWrite Permission = iota
TasksRead Permission = iota
TasksWrite Permission = iota
TeamsRead Permission = iota
TeamsWrite Permission = iota
UsersRead Permission = iota
UsersWrite Permission = iota
UserTaskListsRead Permission = iota
UserTaskListsWrite Permission = iota
MembershipsRead Permission = iota
MembershipsWrite Permission = iota
RulesRead Permission = iota
RulesWrite Permission = iota
)
var (
PermissionStrings = map[Permission]string{
AllocationsRead: "allocations:read",
AllocationsWrite: "allocations:write",
AttachmentsRead: "attachments:read",
AttachmentsWrite: "attachments:write",
AutditLogsRead: "autdit_logs:read",
AutditLogsWrite: "autdit_logs:write",
CustomFieldsRead: "custom_fields:read",
CustomFieldsWrite: "custom_fields:write",
CustomFieldSettingsRead: "custom_field_settings:read",
CustomFieldSettingsWrite: "custom_field_settings:write",
BatchApiRead: "batch_api:read",
BatchApiWrite: "batch_api:write",
EventsRead: "events:read",
EventsWrite: "events:write",
GoalsRead: "goals:read",
GoalsWrite: "goals:write",
JobsRead: "jobs:read",
JobsWrite: "jobs:write",
PortfoliosRead: "portfolios:read",
PortfoliosWrite: "portfolios:write",
ProjectsRead: "projects:read",
ProjectsWrite: "projects:write",
ProjectMembershipsRead: "project_memberships:read",
ProjectMembershipsWrite: "project_memberships:write",
SectionsRead: "sections:read",
SectionsWrite: "sections:write",
TagsRead: "tags:read",
TagsWrite: "tags:write",
TasksRead: "tasks:read",
TasksWrite: "tasks:write",
TeamsRead: "teams:read",
TeamsWrite: "teams:write",
UsersRead: "users:read",
UsersWrite: "users:write",
UserTaskListsRead: "user_task_lists:read",
UserTaskListsWrite: "user_task_lists:write",
MembershipsRead: "memberships:read",
MembershipsWrite: "memberships:write",
RulesRead: "rules:read",
RulesWrite: "rules:write",
}
StringToPermission = map[string]Permission{
"allocations:read": AllocationsRead,
"allocations:write": AllocationsWrite,
"attachments:read": AttachmentsRead,
"attachments:write": AttachmentsWrite,
"autdit_logs:read": AutditLogsRead,
"autdit_logs:write": AutditLogsWrite,
"custom_fields:read": CustomFieldsRead,
"custom_fields:write": CustomFieldsWrite,
"custom_field_settings:read": CustomFieldSettingsRead,
"custom_field_settings:write": CustomFieldSettingsWrite,
"batch_api:read": BatchApiRead,
"batch_api:write": BatchApiWrite,
"events:read": EventsRead,
"events:write": EventsWrite,
"goals:read": GoalsRead,
"goals:write": GoalsWrite,
"jobs:read": JobsRead,
"jobs:write": JobsWrite,
"portfolios:read": PortfoliosRead,
"portfolios:write": PortfoliosWrite,
"projects:read": ProjectsRead,
"projects:write": ProjectsWrite,
"project_memberships:read": ProjectMembershipsRead,
"project_memberships:write": ProjectMembershipsWrite,
"sections:read": SectionsRead,
"sections:write": SectionsWrite,
"tags:read": TagsRead,
"tags:write": TagsWrite,
"tasks:read": TasksRead,
"tasks:write": TasksWrite,
"teams:read": TeamsRead,
"teams:write": TeamsWrite,
"users:read": UsersRead,
"users:write": UsersWrite,
"user_task_lists:read": UserTaskListsRead,
"user_task_lists:write": UserTaskListsWrite,
"memberships:read": MembershipsRead,
"memberships:write": MembershipsWrite,
"rules:read": RulesRead,
"rules:write": RulesWrite,
}
PermissionIDs = map[Permission]int{
AllocationsRead: 1,
AllocationsWrite: 2,
AttachmentsRead: 3,
AttachmentsWrite: 4,
AutditLogsRead: 5,
AutditLogsWrite: 6,
CustomFieldsRead: 7,
CustomFieldsWrite: 8,
CustomFieldSettingsRead: 9,
CustomFieldSettingsWrite: 10,
BatchApiRead: 11,
BatchApiWrite: 12,
EventsRead: 13,
EventsWrite: 14,
GoalsRead: 15,
GoalsWrite: 16,
JobsRead: 17,
JobsWrite: 18,
PortfoliosRead: 19,
PortfoliosWrite: 20,
ProjectsRead: 21,
ProjectsWrite: 22,
ProjectMembershipsRead: 23,
ProjectMembershipsWrite: 24,
SectionsRead: 25,
SectionsWrite: 26,
TagsRead: 27,
TagsWrite: 28,
TasksRead: 29,
TasksWrite: 30,
TeamsRead: 31,
TeamsWrite: 32,
UsersRead: 33,
UsersWrite: 34,
UserTaskListsRead: 35,
UserTaskListsWrite: 36,
MembershipsRead: 37,
MembershipsWrite: 38,
RulesRead: 39,
RulesWrite: 40,
}
IdToPermission = map[int]Permission{
1: AllocationsRead,
2: AllocationsWrite,
3: AttachmentsRead,
4: AttachmentsWrite,
5: AutditLogsRead,
6: AutditLogsWrite,
7: CustomFieldsRead,
8: CustomFieldsWrite,
9: CustomFieldSettingsRead,
10: CustomFieldSettingsWrite,
11: BatchApiRead,
12: BatchApiWrite,
13: EventsRead,
14: EventsWrite,
15: GoalsRead,
16: GoalsWrite,
17: JobsRead,
18: JobsWrite,
19: PortfoliosRead,
20: PortfoliosWrite,
21: ProjectsRead,
22: ProjectsWrite,
23: ProjectMembershipsRead,
24: ProjectMembershipsWrite,
25: SectionsRead,
26: SectionsWrite,
27: TagsRead,
28: TagsWrite,
29: TasksRead,
30: TasksWrite,
31: TeamsRead,
32: TeamsWrite,
33: UsersRead,
34: UsersWrite,
35: UserTaskListsRead,
36: UserTaskListsWrite,
37: MembershipsRead,
38: MembershipsWrite,
39: RulesRead,
40: RulesWrite,
}
)
// ToString converts a Permission enum to its string representation
func (p Permission) ToString() (string, error) {
if str, ok := PermissionStrings[p]; ok {
return str, nil
}
return "", errors.New("invalid permission")
}
// ToID converts a Permission enum to its ID
func (p Permission) ToID() (int, error) {
if id, ok := PermissionIDs[p]; ok {
return id, nil
}
return 0, errors.New("invalid permission")
}
// PermissionFromString converts a string representation to its Permission enum
func PermissionFromString(s string) (Permission, error) {
if p, ok := StringToPermission[s]; ok {
return p, nil
}
return 0, errors.New("invalid permission string")
}
// PermissionFromID converts an ID to its Permission enum
func PermissionFromID(id int) (Permission, error) {
if p, ok := IdToPermission[id]; ok {
return p, nil
}
return 0, errors.New("invalid permission ID")
}

View file

@ -0,0 +1,41 @@
permissions:
- allocations:read
- allocations:write
- attachments:read
- attachments:write
- autdit_logs:read
- autdit_logs:write
- custom_fields:read
- custom_fields:write
- custom_field_settings:read
- custom_field_settings:write
- batch_api:read
- batch_api:write
- events:read
- events:write
- goals:read
- goals:write
- jobs:read
- jobs:write
- portfolios:read
- portfolios:write
- projects:read
- projects:write
- project_memberships:read
- project_memberships:write
- sections:read
- sections:write
- tags:read
- tags:write
- tasks:read
- tasks:write
- teams:read
- teams:write
- users:read
- users:write
- user_task_lists:read
- user_task_lists:write
- memberships:read
- memberships:write
- rules:read
- rules:write

View file

@ -3,10 +3,11 @@ package asanaoauth
import (
"context"
"fmt"
regexp "github.com/wasilibs/go-re2"
"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"
@ -58,6 +59,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
s1.AnalysisInfo = map[string]string{"key": resMatch}
}
}
}

View file

@ -95,6 +95,7 @@ func TestAsanaOauth_FromChunk(t *testing.T) {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
got[i].Raw = nil
got[i].AnalysisInfo = nil
}
if diff := pretty.Compare(got, tt.want); diff != "" {
t.Errorf("AsanaOauth.FromData() %s diff: (-got +want)\n%s", tt.name, diff)