From 81005b9223c780d106d883b3b3a4511ba4186694 Mon Sep 17 00:00:00 2001 From: kashif khan Date: Thu, 23 Jan 2025 17:32:12 +0500 Subject: [PATCH 01/25] added analyzer initial working structure --- pkg/analyzer/analyzers/analyzers.go | 2 + .../analyzers/elevenlabs/elevenlabs.go | 224 ++++++++++++++++++ .../analyzers/elevenlabs/permissions.go | 151 ++++++++++++ .../analyzers/elevenlabs/permissions.yaml | 24 ++ .../analyzers/elevenlabs/resources.go | 40 ++++ .../analyzers/elevenlabs/responses.go | 67 ++++++ pkg/analyzer/cli.go | 3 + 7 files changed, 511 insertions(+) create mode 100644 pkg/analyzer/analyzers/elevenlabs/elevenlabs.go create mode 100644 pkg/analyzer/analyzers/elevenlabs/permissions.go create mode 100644 pkg/analyzer/analyzers/elevenlabs/permissions.yaml create mode 100644 pkg/analyzer/analyzers/elevenlabs/resources.go create mode 100644 pkg/analyzer/analyzers/elevenlabs/responses.go diff --git a/pkg/analyzer/analyzers/analyzers.go b/pkg/analyzer/analyzers/analyzers.go index 06eea241f192..a5670c1944b3 100644 --- a/pkg/analyzer/analyzers/analyzers.go +++ b/pkg/analyzer/analyzers/analyzers.go @@ -63,6 +63,7 @@ const ( AnalyzerTypeAirbrake AnalyzerTypeAsana AnalyzerTypeBitbucket + AnalyzerTypeElevenLabs AnalyzerTypeGitHub AnalyzerTypeGitLab AnalyzerTypeHuggingFace @@ -89,6 +90,7 @@ var analyzerTypeStrings = map[AnalyzerType]string{ AnalyzerTypeAirbrake: "Airbrake", AnalyzerTypeAsana: "Asana", AnalyzerTypeBitbucket: "Bitbucket", + AnalyzerTypeElevenLabs: "ElevenLabs", AnalyzerTypeGitHub: "GitHub", AnalyzerTypeGitLab: "GitLab", AnalyzerTypeHuggingFace: "HuggingFace", diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go new file mode 100644 index 000000000000..795232f0f663 --- /dev/null +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -0,0 +1,224 @@ +package elevenlabs + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + + "github.com/fatih/color" + "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/context" +) + +var _ analyzers.Analyzer = (*Analyzer)(nil) + +type Analyzer struct { + Cfg *config.Config +} + +// SecretInfo hold information about key +type SecretInfo struct { + User User // the owner of key + Valid bool + Reference string + Permissions []string // list of Permissions assigned to the key + Resources Resources // list of resources the key has access to + Misc map[string]string +} + +func (a Analyzer) Type() analyzers.AnalyzerType { + return analyzers.AnalyzerTypeElevenLabs +} + +func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) { + // check if the `key` exist in the credentials info + key, exist := credInfo["key"] + if !exist { + return nil, errors.New("key not found in credentials info") + } + + info, err := AnalyzePermissions(a.Cfg, key) + if err != nil { + return nil, err + } + + return secretInfoToAnalyzerResult(info), nil +} + +// AnalyzePermissions check if key is valid and analyzes the permission for the key +func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) { + // create http client + client := analyzers.NewAnalyzeClient(cfg) + + var secretInfo = &SecretInfo{} + + // valide the key and get user information + secretInfo, valid, err := validateKey(client, key, secretInfo) + if err != nil { + return nil, err + } + + if !valid { + return nil, errors.New("key is not valid") + } + + return secretInfo, nil +} + +func AnalyzeAndPrintPermissions(cfg *config.Config, key string) { + info, err := AnalyzePermissions(cfg, key) + if err != nil { + color.Red("[x] Error : %s", err.Error()) + return + } + + if info.Valid { + color.Green("[!] Valid ElevenLabs API key\n\n") + } + printPermissions(info.Permissions) + if info.Valid { + printUser(info.User) + } + color.Yellow("\n[i] Expires: Never") + +} + +// secretInfoToAnalyzerResult translate secret info to Analyzer Result +func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult { + if info == nil { + return nil + } + + result := analyzers.AnalyzerResult{ + AnalyzerType: analyzers.AnalyzerTypeElevenLabs, + Metadata: map[string]any{}, + Bindings: make([]analyzers.Binding, len(info.Permissions)), + } + + for id, scope := range info.Permissions { + result.Bindings[id] = analyzers.Binding{ + Permission: analyzers.Permission{ + Value: scope, + }, + } + } + result.Metadata["Valid_Key"] = info.Valid + + return &result +} + +// validateKey check if the key is valid and get the user information if it's valid +func validateKey(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, bool, error) { + response, statusCode, err := makeGetRequest(client, permissionToAPIMap[UserRead], key) + if err != nil { + return nil, false, err + } + + if statusCode == http.StatusOK { + var user UserResponse + + if err := json.Unmarshal(response, &user); err != nil { + return nil, false, err + } + + // map info to secretInfo + secretInfo.Valid = true + secretInfo.User.ID = user.UserID + secretInfo.User.Name = user.FirstName + secretInfo.User.SubscriptionTier = user.Subscription.Tier + secretInfo.User.SubscriptionStatus = user.Subscription.Status + // add user read scope to secret info + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[UserRead]) + + return secretInfo, true, nil + } else if statusCode >= http.StatusBadRequest && statusCode <= 499 { + // check if api key is invalid or not verifiable, return false + ok, err := checkErrorStatus(response, InvalidAPIKey, NotVerifiable) + if err != nil { + return nil, false, err + } + + if ok { + return nil, false, nil + } + } + + // if no expected status code was detected + return nil, false, fmt.Errorf("unexpected status code: %d", statusCode) +} + +// CaptureBindings gather permissions assigned to key and the resources it has access to +func CaptureBindings(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, error) { + // history item ids + // dubbings + // voices + // projects + // samples + // pronunciation dictionaries + // models + // audio native + // text to speech + // voice changer + // audio isolation + + return nil, nil +} + +// makeGetRequest send the GET request to passed url with passed key as API Key and return response body and status code +func makeGetRequest(client *http.Client, url, key string) ([]byte, int, error) { + // create request + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, 0, err + } + + // add key in the header + req.Header.Add("xi-api-key", key) + + resp, err := client.Do(req) + if err != nil { + return nil, 0, err + } + + defer func() { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() + + /* + the reason to translate body to byte and does not directly return http.Response + is if we return http.Response we cannot close the body in defer. If we do we will get an error + when reading body outside this function + */ + responseBodyByte, err := io.ReadAll(resp.Body) + if err != nil { + return nil, 0, err + } + + return responseBodyByte, resp.StatusCode, nil +} + +func printPermissions(permissions []string) { + color.Yellow("[i] Permissions:") + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"Permission"}) + for _, permission := range permissions { + t.AppendRow(table.Row{color.GreenString(permission)}) + } + t.Render() +} + +func printUser(user User) { + color.Green("\n[i] User:") + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"ID", "Name", "Subscription Tier", "Subscription Status"}) + t.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.Name), color.GreenString(user.SubscriptionTier), color.GreenString(user.SubscriptionStatus)}) + t.Render() +} diff --git a/pkg/analyzer/analyzers/elevenlabs/permissions.go b/pkg/analyzer/analyzers/elevenlabs/permissions.go new file mode 100644 index 000000000000..87af32ce6021 --- /dev/null +++ b/pkg/analyzer/analyzers/elevenlabs/permissions.go @@ -0,0 +1,151 @@ +// Code generated by go generate; DO NOT EDIT. +package elevenlabs + +import "errors" + +type Permission int + +const ( + Invalid Permission = iota + TextToSpeech Permission = iota + SpeechToSpeech Permission = iota + SoundGeneration Permission = iota + AudioIsolation Permission = iota + DubbingRead Permission = iota + DubbingWrite Permission = iota + ProjectsRead Permission = iota + ProjectsWrite Permission = iota + AudioNativeRead Permission = iota + AudioNativeWrite Permission = iota + PronunciationDictionariesRead Permission = iota + PronunciationDictionariesWrite Permission = iota + VoicesRead Permission = iota + VoicesWrite Permission = iota + ModelsRead Permission = iota + SpeechHistoryRead Permission = iota + SpeechHistoryWrite Permission = iota + UserRead Permission = iota + WorkspaceWrite Permission = iota +) + +var ( + PermissionStrings = map[Permission]string{ + TextToSpeech: "text_to_speech", + SpeechToSpeech: "speech_to_speech", + SoundGeneration: "sound_generation", + AudioIsolation: "audio_isolation", + DubbingRead: "dubbing_read", + DubbingWrite: "dubbing_write", + ProjectsRead: "projects_read", + ProjectsWrite: "projects_write", + AudioNativeRead: "audio_native_read", + AudioNativeWrite: "audio_native_write", + PronunciationDictionariesRead: "pronunciation_dictionaries_read", + PronunciationDictionariesWrite: "pronunciation_dictionaries_write", + VoicesRead: "voices_read", + VoicesWrite: "voices_write", + ModelsRead: "models_read", + SpeechHistoryRead: "speech_history_read", + SpeechHistoryWrite: "speech_history_write", + UserRead: "user_read", + WorkspaceWrite: "workspace_write", + } + + StringToPermission = map[string]Permission{ + "text_to_speech": TextToSpeech, + "speech_to_speech": SpeechToSpeech, + "sound_generation": SoundGeneration, + "audio_isolation": AudioIsolation, + "dubbing_read": DubbingRead, + "dubbing_write": DubbingWrite, + "projects_read": ProjectsRead, + "projects_write": ProjectsWrite, + "audio_native_read": AudioNativeRead, + "audio_native_write": AudioNativeWrite, + "pronunciation_dictionaries_read": PronunciationDictionariesRead, + "pronunciation_dictionaries_write": PronunciationDictionariesWrite, + "voices_read": VoicesRead, + "voices_write": VoicesWrite, + "models_read": ModelsRead, + "speech_history_read": SpeechHistoryRead, + "speech_history_write": SpeechHistoryWrite, + "user_read": UserRead, + "workspace_write": WorkspaceWrite, + } + + PermissionIDs = map[Permission]int{ + TextToSpeech: 1, + SpeechToSpeech: 2, + SoundGeneration: 3, + AudioIsolation: 4, + DubbingRead: 5, + DubbingWrite: 6, + ProjectsRead: 7, + ProjectsWrite: 8, + AudioNativeRead: 9, + AudioNativeWrite: 10, + PronunciationDictionariesRead: 11, + PronunciationDictionariesWrite: 12, + VoicesRead: 13, + VoicesWrite: 14, + ModelsRead: 15, + SpeechHistoryRead: 16, + SpeechHistoryWrite: 17, + UserRead: 18, + WorkspaceWrite: 19, + } + + IdToPermission = map[int]Permission{ + 1: TextToSpeech, + 2: SpeechToSpeech, + 3: SoundGeneration, + 4: AudioIsolation, + 5: DubbingRead, + 6: DubbingWrite, + 7: ProjectsRead, + 8: ProjectsWrite, + 9: AudioNativeRead, + 10: AudioNativeWrite, + 11: PronunciationDictionariesRead, + 12: PronunciationDictionariesWrite, + 13: VoicesRead, + 14: VoicesWrite, + 15: ModelsRead, + 16: SpeechHistoryRead, + 17: SpeechHistoryWrite, + 18: UserRead, + 19: WorkspaceWrite, + } +) + +// 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") +} diff --git a/pkg/analyzer/analyzers/elevenlabs/permissions.yaml b/pkg/analyzer/analyzers/elevenlabs/permissions.yaml new file mode 100644 index 000000000000..ec08726c83f6 --- /dev/null +++ b/pkg/analyzer/analyzers/elevenlabs/permissions.yaml @@ -0,0 +1,24 @@ +permissions: + - text_to_speech + - speech_to_speech + - sound_generation + - audio_isolation + # - voice_generation + - dubbing_read + - dubbing_write + - projects_read + - projects_write + - audio_native_read + - audio_native_write + - pronunciation_dictionaries_read + - pronunciation_dictionaries_write + - voices_read + - voices_write + - models_read + # - models_write + - speech_history_read + - speech_history_write + - user_read + # - user_write + # - workspace_read + - workspace_write diff --git a/pkg/analyzer/analyzers/elevenlabs/resources.go b/pkg/analyzer/analyzers/elevenlabs/resources.go new file mode 100644 index 000000000000..26f65315d545 --- /dev/null +++ b/pkg/analyzer/analyzers/elevenlabs/resources.go @@ -0,0 +1,40 @@ +package elevenlabs + +// User hold the information about user to whom the key belongs to +type User struct { + ID string + Name string + SubscriptionTier string + SubscriptionStatus string +} + +// Resources hold information about all the resources the key has access to +type Resources struct { + HistoryItemsID []string + Voices []Voice + Projects []Project + ProununciationDictionaiesID []string + Models []Models + Dubbings []Dubbing +} + +type Voice struct { + ID string + PublicOwnerID string // only for shared voices + Name string +} + +type Project struct { + ID string + Name string +} + +type Models struct { + ID string + Name string +} + +type Dubbing struct { + ID string + Name string +} diff --git a/pkg/analyzer/analyzers/elevenlabs/responses.go b/pkg/analyzer/analyzers/elevenlabs/responses.go new file mode 100644 index 000000000000..f9bb2b93d8e4 --- /dev/null +++ b/pkg/analyzer/analyzers/elevenlabs/responses.go @@ -0,0 +1,67 @@ +package elevenlabs + +import ( + "encoding/json" + "slices" +) + +// permissionToAPIMap contain the API endpoints for each scope/permission +var permissionToAPIMap = map[Permission]string{ + TextToSpeech: "https://api.elevenlabs.io/v1/text-to-speech/%s", // require voice id + SpeechToSpeech: "", + SoundGeneration: "", + AudioIsolation: "", + DubbingRead: "", + DubbingWrite: "", + ProjectsRead: "", + ProjectsWrite: "", + AudioNativeRead: "", + AudioNativeWrite: "", + PronunciationDictionariesRead: "", + PronunciationDictionariesWrite: "", + VoicesRead: "", + VoicesWrite: "", + ModelsRead: "", + SpeechHistoryRead: "", + SpeechHistoryWrite: "", + UserRead: "https://api.elevenlabs.io/v1/user", + WorkspaceWrite: "", +} + +var ( + // error statuses + NotVerifiable = "api_key_not_verifiable" + InvalidAPIKey = "invalid_api_key" +) + +// ErrorResponse is the error response for all APIs +type ErrorResponse struct { + Detail struct { + Status string `json:"status"` + } `json:"detail"` +} + +// UserResponse is the /user API response +type UserResponse struct { + UserID string `json:"user_id"` + FirstName string `json:"first_name"` + Subscription struct { + Tier string `json:"tier"` + Status string `json:"status"` + } `json:"subscription"` +} + +// checkErrorStatus check if any of expected error status exist in actual API error response +func checkErrorStatus(response []byte, expectedStatuses ...string) (bool, error) { + var errorResp ErrorResponse + + if err := json.Unmarshal(response, &errorResp); err != nil { + return false, err + } + + if slices.Contains(expectedStatuses, errorResp.Detail.Status) { + return true, nil + } + + return false, nil +} diff --git a/pkg/analyzer/cli.go b/pkg/analyzer/cli.go index d3f8004e4a47..ca3649e9c2a6 100644 --- a/pkg/analyzer/cli.go +++ b/pkg/analyzer/cli.go @@ -9,6 +9,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airbrake" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/asana" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/bitbucket" + "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/elevenlabs" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/gitlab" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/huggingface" @@ -102,5 +103,7 @@ func Run(cmd string) { shopify.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"], secretInfo.Parts["url"]) case "opsgenie": opsgenie.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"]) + case "elevenlabs": + elevenlabs.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"]) } } From 794d1764c9584549ac277bd183bf94d69078b75d Mon Sep 17 00:00:00 2001 From: kashif khan Date: Thu, 23 Jan 2025 18:54:07 +0500 Subject: [PATCH 02/25] restructured the resources --- .../analyzers/elevenlabs/elevenlabs.go | 67 +++++++++++++++---- .../analyzers/elevenlabs/resources.go | 36 ++-------- 2 files changed, 60 insertions(+), 43 deletions(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index 795232f0f663..9cf68b6b9d47 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -26,8 +26,8 @@ type SecretInfo struct { User User // the owner of key Valid bool Reference string - Permissions []string // list of Permissions assigned to the key - Resources Resources // list of resources the key has access to + Permissions []string // list of Permissions assigned to the key + Resources []Resource // list of resources the key has access to Misc map[string]string } @@ -80,10 +80,16 @@ func AnalyzeAndPrintPermissions(cfg *config.Config, key string) { if info.Valid { color.Green("[!] Valid ElevenLabs API key\n\n") } - printPermissions(info.Permissions) + + // print user information if info.Valid { printUser(info.User) } + + // print permissions + printPermissions(info.Permissions) + // print resources + printResources(info.Resources) color.Yellow("\n[i] Expires: Never") } @@ -100,13 +106,26 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult { Bindings: make([]analyzers.Binding, len(info.Permissions)), } - for id, scope := range info.Permissions { - result.Bindings[id] = analyzers.Binding{ + // extract information from resource to create bindings and append to result bindings + for _, resource := range info.Resources { + binding := analyzers.Binding{ + Resource: analyzers.Resource{ + Name: resource.Name, + FullyQualifiedName: resource.ID, + Type: resource.Type, + }, Permission: analyzers.Permission{ - Value: scope, + Value: resource.Permission, }, } + + for key, value := range resource.Metadata { + binding.Resource.Metadata[key] = value + } + + result.Bindings = append(result.Bindings, binding) } + result.Metadata["Valid_Key"] = info.Valid return &result @@ -128,12 +147,21 @@ func validateKey(client *http.Client, key string, secretInfo *SecretInfo) (*Secr // map info to secretInfo secretInfo.Valid = true - secretInfo.User.ID = user.UserID - secretInfo.User.Name = user.FirstName - secretInfo.User.SubscriptionTier = user.Subscription.Tier - secretInfo.User.SubscriptionStatus = user.Subscription.Status + secretInfo.User = User{ + ID: user.UserID, + Name: user.FirstName, + SubscriptionTier: user.Subscription.Tier, + SubscriptionStatus: user.Subscription.Status, + } // add user read scope to secret info secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[UserRead]) + // map resource to secret info + secretInfo.Resources = append(secretInfo.Resources, Resource{ + ID: user.UserID, + Name: user.FirstName, + Type: "User", + Permission: PermissionStrings[UserRead], + }) return secretInfo, true, nil } else if statusCode >= http.StatusBadRequest && statusCode <= 499 { @@ -203,6 +231,15 @@ func makeGetRequest(client *http.Client, url, key string) ([]byte, int, error) { return responseBodyByte, resp.StatusCode, nil } +func printUser(user User) { + color.Green("\n[i] User:") + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"ID", "Name", "Subscription Tier", "Subscription Status"}) + t.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.Name), color.GreenString(user.SubscriptionTier), color.GreenString(user.SubscriptionStatus)}) + t.Render() +} + func printPermissions(permissions []string) { color.Yellow("[i] Permissions:") t := table.NewWriter() @@ -214,11 +251,13 @@ func printPermissions(permissions []string) { t.Render() } -func printUser(user User) { - color.Green("\n[i] User:") +func printResources(resources []Resource) { + color.Green("\n[i] Resources:") t := table.NewWriter() t.SetOutputMirror(os.Stdout) - t.AppendHeader(table.Row{"ID", "Name", "Subscription Tier", "Subscription Status"}) - t.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.Name), color.GreenString(user.SubscriptionTier), color.GreenString(user.SubscriptionStatus)}) + t.AppendHeader(table.Row{"Resource Type", "Resource ID", "Resource Name", "Permission"}) + for _, resource := range resources { + t.AppendRow(table.Row{color.GreenString(resource.Type), color.GreenString(resource.ID), color.GreenString(resource.Name), color.GreenString(resource.Permission)}) + } t.Render() } diff --git a/pkg/analyzer/analyzers/elevenlabs/resources.go b/pkg/analyzer/analyzers/elevenlabs/resources.go index 26f65315d545..730080552732 100644 --- a/pkg/analyzer/analyzers/elevenlabs/resources.go +++ b/pkg/analyzer/analyzers/elevenlabs/resources.go @@ -8,33 +8,11 @@ type User struct { SubscriptionStatus string } -// Resources hold information about all the resources the key has access to -type Resources struct { - HistoryItemsID []string - Voices []Voice - Projects []Project - ProununciationDictionaiesID []string - Models []Models - Dubbings []Dubbing -} - -type Voice struct { - ID string - PublicOwnerID string // only for shared voices - Name string -} - -type Project struct { - ID string - Name string -} - -type Models struct { - ID string - Name string -} - -type Dubbing struct { - ID string - Name string +// Resources hold information about the resources the key has access +type Resource struct { + ID string + Name string + Type string + Metadata map[string]string + Permission string } From f6c146c00699b244e28a9bb557e8baad7f9fedfb Mon Sep 17 00:00:00 2001 From: kashif khan Date: Thu, 23 Jan 2025 20:01:13 +0500 Subject: [PATCH 03/25] added history read api --- .../analyzers/elevenlabs/elevenlabs.go | 77 ++++------ pkg/analyzer/analyzers/elevenlabs/requests.go | 142 ++++++++++++++++++ .../analyzers/elevenlabs/responses.go | 67 --------- 3 files changed, 169 insertions(+), 117 deletions(-) create mode 100644 pkg/analyzer/analyzers/elevenlabs/requests.go delete mode 100644 pkg/analyzer/analyzers/elevenlabs/responses.go diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index 9cf68b6b9d47..6c6ba074408c 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "io" "net/http" "os" @@ -57,7 +56,7 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) { var secretInfo = &SecretInfo{} - // valide the key and get user information + // validate the key and get user information secretInfo, valid, err := validateKey(client, key, secretInfo) if err != nil { return nil, err @@ -67,6 +66,12 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) { return nil, errors.New("key is not valid") } + // Get resources + secretInfo, err = getResources(client, key, secretInfo) + if err != nil { + return nil, nil + } + return secretInfo, nil } @@ -77,21 +82,22 @@ func AnalyzeAndPrintPermissions(cfg *config.Config, key string) { return } - if info.Valid { - color.Green("[!] Valid ElevenLabs API key\n\n") + if info == nil { + color.Red("[x] Error : %s", "No information found") + return } - // print user information if info.Valid { + color.Green("[!] Valid ElevenLabs API key\n\n") + // print user information printUser(info.User) - } - - // print permissions - printPermissions(info.Permissions) - // print resources - printResources(info.Resources) - color.Yellow("\n[i] Expires: Never") + // print permissions + printPermissions(info.Permissions) + // print resources + printResources(info.Resources) + color.Yellow("\n[i] Expires: Never") + } } // secretInfoToAnalyzerResult translate secret info to Analyzer Result @@ -180,9 +186,14 @@ func validateKey(client *http.Client, key string, secretInfo *SecretInfo) (*Secr return nil, false, fmt.Errorf("unexpected status code: %d", statusCode) } -// CaptureBindings gather permissions assigned to key and the resources it has access to -func CaptureBindings(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, error) { - // history item ids +// getResources gather resources the key can access +func getResources(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, error) { + // history + var err error + secretInfo, err = getHistory(client, key, secretInfo) + if err != nil { + return secretInfo, err + } // dubbings // voices // projects @@ -194,41 +205,7 @@ func CaptureBindings(client *http.Client, key string, secretInfo *SecretInfo) (* // voice changer // audio isolation - return nil, nil -} - -// makeGetRequest send the GET request to passed url with passed key as API Key and return response body and status code -func makeGetRequest(client *http.Client, url, key string) ([]byte, int, error) { - // create request - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return nil, 0, err - } - - // add key in the header - req.Header.Add("xi-api-key", key) - - resp, err := client.Do(req) - if err != nil { - return nil, 0, err - } - - defer func() { - _, _ = io.Copy(io.Discard, resp.Body) - _ = resp.Body.Close() - }() - - /* - the reason to translate body to byte and does not directly return http.Response - is if we return http.Response we cannot close the body in defer. If we do we will get an error - when reading body outside this function - */ - responseBodyByte, err := io.ReadAll(resp.Body) - if err != nil { - return nil, 0, err - } - - return responseBodyByte, resp.StatusCode, nil + return secretInfo, nil } func printUser(user User) { diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go new file mode 100644 index 000000000000..6622414bbdd7 --- /dev/null +++ b/pkg/analyzer/analyzers/elevenlabs/requests.go @@ -0,0 +1,142 @@ +package elevenlabs + +import ( + "encoding/json" + "io" + "net/http" + "slices" +) + +// permissionToAPIMap contain the API endpoints for each scope/permission +var permissionToAPIMap = map[Permission]string{ + TextToSpeech: "https://api.elevenlabs.io/v1/text-to-speech/%s", // require voice id + SpeechToSpeech: "", + SoundGeneration: "", + AudioIsolation: "", + DubbingRead: "", + DubbingWrite: "", + ProjectsRead: "", + ProjectsWrite: "", + AudioNativeRead: "", + AudioNativeWrite: "", + PronunciationDictionariesRead: "", + PronunciationDictionariesWrite: "", + VoicesRead: "", + VoicesWrite: "", + ModelsRead: "", + SpeechHistoryRead: "https://api.elevenlabs.io/v1/history", + SpeechHistoryWrite: "https://api.elevenlabs.io/v1/history/%s", // require history item id + UserRead: "https://api.elevenlabs.io/v1/user", + WorkspaceWrite: "", +} + +var ( + // error statuses + NotVerifiable = "api_key_not_verifiable" + InvalidAPIKey = "invalid_api_key" +) + +// ErrorResponse is the error response for all APIs +type ErrorResponse struct { + Detail struct { + Status string `json:"status"` + } `json:"detail"` +} + +// UserResponse is the /user API response +type UserResponse struct { + UserID string `json:"user_id"` + FirstName string `json:"first_name"` + Subscription struct { + Tier string `json:"tier"` + Status string `json:"status"` + } `json:"subscription"` +} + +// HistoryResponse is the /history API response +type HistoryResponse struct { + History []struct { + HistoryItemID string `json:"history_item_id"` + ModelID string `json:"model_id"` + VoiceID string `json:"voice_id"` + } `json:"history"` +} + +// getHistory get history item using the key passed and add them to secret info +func getHistory(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, error) { + historyResponse, statusCode, err := makeGetRequest(client, permissionToAPIMap[SpeechHistoryRead], key) + if err != nil { + return nil, err + } + + if statusCode == http.StatusOK { + var history HistoryResponse + + if err := json.Unmarshal(historyResponse, &history); err != nil { + return nil, err + } + + // add history read scope to secret info + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[SpeechHistoryRead]) + // map resource to secret info + for _, historyItem := range history.History { + secretInfo.Resources = append(secretInfo.Resources, Resource{ + ID: historyItem.HistoryItemID, + Name: "", // no name + Type: "History", + Permission: PermissionStrings[SpeechHistoryRead], + }) + } + } + + return secretInfo, nil +} + +// makeGetRequest send the GET request to passed url with passed key as API Key and return response body and status code +func makeGetRequest(client *http.Client, url, key string) ([]byte, int, error) { + // create request + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, 0, err + } + + // add key in the header + req.Header.Add("xi-api-key", key) + + resp, err := client.Do(req) + if err != nil { + return nil, 0, err + } + + defer func() { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() + + /* + the reason to translate body to byte and does not directly return http.Response + is if we return http.Response we cannot close the body in defer. If we do we will get an error + when reading body outside this function + */ + responseBodyByte, err := io.ReadAll(resp.Body) + if err != nil { + return nil, 0, err + } + + return responseBodyByte, resp.StatusCode, nil +} + +// checkErrorStatus check if any of expected error status exist in actual API error response +func checkErrorStatus(response []byte, expectedStatuses ...string) (bool, error) { + var errorResp ErrorResponse + + if err := json.Unmarshal(response, &errorResp); err != nil { + return false, err + } + + if slices.Contains(expectedStatuses, errorResp.Detail.Status) { + return true, nil + } + + return false, nil +} diff --git a/pkg/analyzer/analyzers/elevenlabs/responses.go b/pkg/analyzer/analyzers/elevenlabs/responses.go deleted file mode 100644 index f9bb2b93d8e4..000000000000 --- a/pkg/analyzer/analyzers/elevenlabs/responses.go +++ /dev/null @@ -1,67 +0,0 @@ -package elevenlabs - -import ( - "encoding/json" - "slices" -) - -// permissionToAPIMap contain the API endpoints for each scope/permission -var permissionToAPIMap = map[Permission]string{ - TextToSpeech: "https://api.elevenlabs.io/v1/text-to-speech/%s", // require voice id - SpeechToSpeech: "", - SoundGeneration: "", - AudioIsolation: "", - DubbingRead: "", - DubbingWrite: "", - ProjectsRead: "", - ProjectsWrite: "", - AudioNativeRead: "", - AudioNativeWrite: "", - PronunciationDictionariesRead: "", - PronunciationDictionariesWrite: "", - VoicesRead: "", - VoicesWrite: "", - ModelsRead: "", - SpeechHistoryRead: "", - SpeechHistoryWrite: "", - UserRead: "https://api.elevenlabs.io/v1/user", - WorkspaceWrite: "", -} - -var ( - // error statuses - NotVerifiable = "api_key_not_verifiable" - InvalidAPIKey = "invalid_api_key" -) - -// ErrorResponse is the error response for all APIs -type ErrorResponse struct { - Detail struct { - Status string `json:"status"` - } `json:"detail"` -} - -// UserResponse is the /user API response -type UserResponse struct { - UserID string `json:"user_id"` - FirstName string `json:"first_name"` - Subscription struct { - Tier string `json:"tier"` - Status string `json:"status"` - } `json:"subscription"` -} - -// checkErrorStatus check if any of expected error status exist in actual API error response -func checkErrorStatus(response []byte, expectedStatuses ...string) (bool, error) { - var errorResp ErrorResponse - - if err := json.Unmarshal(response, &errorResp); err != nil { - return false, err - } - - if slices.Contains(expectedStatuses, errorResp.Detail.Status) { - return true, nil - } - - return false, nil -} From 75071f410c21cfff66387e3d0d62c73bb148e547 Mon Sep 17 00:00:00 2001 From: kashif khan Date: Fri, 24 Jan 2025 12:02:48 +0500 Subject: [PATCH 04/25] added history delete api --- .../analyzers/elevenlabs/elevenlabs.go | 23 +++++ pkg/analyzer/analyzers/elevenlabs/requests.go | 98 +++++++++++++------ .../analyzers/elevenlabs/resources.go | 18 ---- 3 files changed, 93 insertions(+), 46 deletions(-) delete mode 100644 pkg/analyzer/analyzers/elevenlabs/resources.go diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index 6c6ba074408c..1f3ad397800e 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -30,6 +30,23 @@ type SecretInfo struct { Misc map[string]string } +// User hold the information about user to whom the key belongs to +type User struct { + ID string + Name string + SubscriptionTier string + SubscriptionStatus string +} + +// Resources hold information about the resources the key has access +type Resource struct { + ID string + Name string + Type string + Metadata map[string]string + Permission string +} + func (a Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeElevenLabs } @@ -194,6 +211,11 @@ func getResources(client *http.Client, key string, secretInfo *SecretInfo) (*Sec if err != nil { return secretInfo, err } + + secretInfo, err = deleteHistory(client, key, secretInfo) + if err != nil { + return secretInfo, err + } // dubbings // voices // projects @@ -208,6 +230,7 @@ func getResources(client *http.Client, key string, secretInfo *SecretInfo) (*Sec return secretInfo, nil } +// cli print functions func printUser(user User) { color.Green("\n[i] User:") t := table.NewWriter() diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go index 6622414bbdd7..642eb64b4909 100644 --- a/pkg/analyzer/analyzers/elevenlabs/requests.go +++ b/pkg/analyzer/analyzers/elevenlabs/requests.go @@ -2,12 +2,15 @@ package elevenlabs import ( "encoding/json" + "fmt" "io" "net/http" "slices" + "strings" ) // permissionToAPIMap contain the API endpoints for each scope/permission +// api docs: https://elevenlabs.io/docs/api-reference/introduction var permissionToAPIMap = map[Permission]string{ TextToSpeech: "https://api.elevenlabs.io/v1/text-to-speech/%s", // require voice id SpeechToSpeech: "", @@ -31,9 +34,12 @@ var permissionToAPIMap = map[Permission]string{ } var ( + // not exist key + fakeID = "_thou_shalt_not_exist_" // error statuses - NotVerifiable = "api_key_not_verifiable" - InvalidAPIKey = "invalid_api_key" + NotVerifiable = "api_key_not_verifiable" + InvalidAPIKey = "invalid_api_key" + MissingPermissions = "missing_permissions" ) // ErrorResponse is the error response for all APIs @@ -62,34 +68,14 @@ type HistoryResponse struct { } `json:"history"` } -// getHistory get history item using the key passed and add them to secret info -func getHistory(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, error) { - historyResponse, statusCode, err := makeGetRequest(client, permissionToAPIMap[SpeechHistoryRead], key) - if err != nil { - return nil, err +// getAPIUrl return the API Url mapped to the permission +func getAPIUrl(permission Permission) string { + apiUrl := permissionToAPIMap[permission] + if strings.Contains(apiUrl, "%s") { + return fmt.Sprintf(apiUrl, fakeID) } - if statusCode == http.StatusOK { - var history HistoryResponse - - if err := json.Unmarshal(historyResponse, &history); err != nil { - return nil, err - } - - // add history read scope to secret info - secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[SpeechHistoryRead]) - // map resource to secret info - for _, historyItem := range history.History { - secretInfo.Resources = append(secretInfo.Resources, Resource{ - ID: historyItem.HistoryItemID, - Name: "", // no name - Type: "History", - Permission: PermissionStrings[SpeechHistoryRead], - }) - } - } - - return secretInfo, nil + return apiUrl } // makeGetRequest send the GET request to passed url with passed key as API Key and return response body and status code @@ -126,6 +112,62 @@ func makeGetRequest(client *http.Client, url, key string) ([]byte, int, error) { return responseBodyByte, resp.StatusCode, nil } +// getHistory get history item using the key passed and add them to secret info +func getHistory(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, error) { + response, statusCode, err := makeGetRequest(client, getAPIUrl(SpeechHistoryRead), key) + if err != nil { + return nil, err + } + + if statusCode == http.StatusOK { + var history HistoryResponse + + if err := json.Unmarshal(response, &history); err != nil { + return nil, err + } + + // add history read scope to secret info + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[SpeechHistoryRead]) + // map resource to secret info + for _, historyItem := range history.History { + secretInfo.Resources = append(secretInfo.Resources, Resource{ + ID: historyItem.HistoryItemID, + Name: "", // no name + Type: "History", + Permission: PermissionStrings[SpeechHistoryRead], + }) + } + } + + return secretInfo, nil +} + +// deleteHistory try to delete a history item. The item must not exist. +func deleteHistory(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, error) { + response, statusCode, err := makeGetRequest(client, getAPIUrl(SpeechHistoryWrite), key) + if err != nil { + return nil, err + } + + if statusCode >= http.StatusBadRequest && statusCode <= 499 { + // check if status in response is not missing permissions + ok, err := checkErrorStatus(response, MissingPermissions) + if err != nil { + return nil, err + } + + // if it's missing permissions return + if ok { + return secretInfo, nil + } + } + + // add history write scope to secret info + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[SpeechHistoryWrite]) + + return secretInfo, nil +} + // checkErrorStatus check if any of expected error status exist in actual API error response func checkErrorStatus(response []byte, expectedStatuses ...string) (bool, error) { var errorResp ErrorResponse diff --git a/pkg/analyzer/analyzers/elevenlabs/resources.go b/pkg/analyzer/analyzers/elevenlabs/resources.go deleted file mode 100644 index 730080552732..000000000000 --- a/pkg/analyzer/analyzers/elevenlabs/resources.go +++ /dev/null @@ -1,18 +0,0 @@ -package elevenlabs - -// User hold the information about user to whom the key belongs to -type User struct { - ID string - Name string - SubscriptionTier string - SubscriptionStatus string -} - -// Resources hold information about the resources the key has access -type Resource struct { - ID string - Name string - Type string - Metadata map[string]string - Permission string -} From f61faf260bfe76df44f0a10a72653e1c7b9eba26 Mon Sep 17 00:00:00 2001 From: kashif khan Date: Fri, 24 Jan 2025 12:19:43 +0500 Subject: [PATCH 05/25] fixed http method issue --- pkg/analyzer/analyzers/elevenlabs/elevenlabs.go | 2 +- pkg/analyzer/analyzers/elevenlabs/requests.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index 1f3ad397800e..c27b5f091e94 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -156,7 +156,7 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult { // validateKey check if the key is valid and get the user information if it's valid func validateKey(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, bool, error) { - response, statusCode, err := makeGetRequest(client, permissionToAPIMap[UserRead], key) + response, statusCode, err := makeElevenLabsRequest(client, permissionToAPIMap[UserRead], http.MethodGet, key) if err != nil { return nil, false, err } diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go index 642eb64b4909..4761710507c3 100644 --- a/pkg/analyzer/analyzers/elevenlabs/requests.go +++ b/pkg/analyzer/analyzers/elevenlabs/requests.go @@ -78,10 +78,10 @@ func getAPIUrl(permission Permission) string { return apiUrl } -// makeGetRequest send the GET request to passed url with passed key as API Key and return response body and status code -func makeGetRequest(client *http.Client, url, key string) ([]byte, int, error) { +// makeElevenLabsRequest send the API request to passed url with passed key as API Key and return response body and status code +func makeElevenLabsRequest(client *http.Client, url, method, key string) ([]byte, int, error) { // create request - req, err := http.NewRequest(http.MethodGet, url, nil) + req, err := http.NewRequest(method, url, nil) if err != nil { return nil, 0, err } @@ -114,7 +114,7 @@ func makeGetRequest(client *http.Client, url, key string) ([]byte, int, error) { // getHistory get history item using the key passed and add them to secret info func getHistory(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, error) { - response, statusCode, err := makeGetRequest(client, getAPIUrl(SpeechHistoryRead), key) + response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(SpeechHistoryRead), http.MethodGet, key) if err != nil { return nil, err } @@ -144,7 +144,7 @@ func getHistory(client *http.Client, key string, secretInfo *SecretInfo) (*Secre // deleteHistory try to delete a history item. The item must not exist. func deleteHistory(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, error) { - response, statusCode, err := makeGetRequest(client, getAPIUrl(SpeechHistoryWrite), key) + response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(SpeechHistoryWrite), http.MethodDelete, key) if err != nil { return nil, err } From 759d9c7bfc6981b5537ade9021db8962ec1013c2 Mon Sep 17 00:00:00 2001 From: kashif khan Date: Fri, 24 Jan 2025 12:58:11 +0500 Subject: [PATCH 06/25] added delete dubbing api and refactored the code --- .../analyzers/elevenlabs/elevenlabs.go | 58 ++++++++++++------- pkg/analyzer/analyzers/elevenlabs/requests.go | 49 ++++++++++++---- 2 files changed, 76 insertions(+), 31 deletions(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index c27b5f091e94..12d91d95deba 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -6,9 +6,11 @@ import ( "fmt" "net/http" "os" + "slices" "github.com/fatih/color" "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/context" @@ -74,7 +76,7 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) { var secretInfo = &SecretInfo{} // validate the key and get user information - secretInfo, valid, err := validateKey(client, key, secretInfo) + valid, err := validateKey(client, key, secretInfo) if err != nil { return nil, err } @@ -84,8 +86,7 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) { } // Get resources - secretInfo, err = getResources(client, key, secretInfo) - if err != nil { + if err := getResources(client, key, secretInfo); err != nil { return nil, nil } @@ -155,17 +156,17 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult { } // validateKey check if the key is valid and get the user information if it's valid -func validateKey(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, bool, error) { +func validateKey(client *http.Client, key string, secretInfo *SecretInfo) (bool, error) { response, statusCode, err := makeElevenLabsRequest(client, permissionToAPIMap[UserRead], http.MethodGet, key) if err != nil { - return nil, false, err + return false, err } if statusCode == http.StatusOK { var user UserResponse if err := json.Unmarshal(response, &user); err != nil { - return nil, false, err + return false, err } // map info to secretInfo @@ -186,37 +187,47 @@ func validateKey(client *http.Client, key string, secretInfo *SecretInfo) (*Secr Permission: PermissionStrings[UserRead], }) - return secretInfo, true, nil + return true, nil } else if statusCode >= http.StatusBadRequest && statusCode <= 499 { // check if api key is invalid or not verifiable, return false ok, err := checkErrorStatus(response, InvalidAPIKey, NotVerifiable) if err != nil { - return nil, false, err + return false, err } if ok { - return nil, false, nil + return false, nil } } // if no expected status code was detected - return nil, false, fmt.Errorf("unexpected status code: %d", statusCode) + return false, fmt.Errorf("unexpected status code: %d", statusCode) } -// getResources gather resources the key can access -func getResources(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, error) { +/* +getResources gather resources the key can access + +Note: The permissions in eleven labs is either Read or Read and Write. There is not separate permission for Write. +If a particular write permission exist that means the read also exist. +So for API calls that does not return any resource data, we make the write permissions API calls first +and if they were as expected we skip the read API calls and add read permission directly. +If write permission API calls was not as expected than only we make read permission API calls +This we only do for those API calls which does not add any resources to secretInfo +*/ +func getResources(client *http.Client, key string, secretInfo *SecretInfo) error { // history - var err error - secretInfo, err = getHistory(client, key, secretInfo) - if err != nil { - return secretInfo, err + if err := getHistory(client, key, secretInfo); err != nil { + return err } - secretInfo, err = deleteHistory(client, key, secretInfo) - if err != nil { - return secretInfo, err + if err := deleteHistory(client, key, secretInfo); err != nil { + return err } + // dubbings + if err := deleteDubbing(client, key, secretInfo); err != nil { + return err + } // voices // projects // samples @@ -227,7 +238,14 @@ func getResources(client *http.Client, key string, secretInfo *SecretInfo) (*Sec // voice changer // audio isolation - return secretInfo, nil + return nil +} + +// permissionExist returns if particular permission exist in the list +func permissionExist(permissionsList []string, permission Permission) bool { + permissionString, _ := permission.ToString() + + return slices.Contains(permissionsList, permissionString) } // cli print functions diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go index 4761710507c3..34fe2b29d126 100644 --- a/pkg/analyzer/analyzers/elevenlabs/requests.go +++ b/pkg/analyzer/analyzers/elevenlabs/requests.go @@ -16,8 +16,8 @@ var permissionToAPIMap = map[Permission]string{ SpeechToSpeech: "", SoundGeneration: "", AudioIsolation: "", - DubbingRead: "", - DubbingWrite: "", + DubbingRead: "https://api.elevenlabs.io/v1/dubbing/%s", // require dubbing id + DubbingWrite: "https://api.elevenlabs.io/v1/dubbing/%s", // require dubbing id ProjectsRead: "", ProjectsWrite: "", AudioNativeRead: "", @@ -113,17 +113,17 @@ func makeElevenLabsRequest(client *http.Client, url, method, key string) ([]byte } // getHistory get history item using the key passed and add them to secret info -func getHistory(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, error) { +func getHistory(client *http.Client, key string, secretInfo *SecretInfo) error { response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(SpeechHistoryRead), http.MethodGet, key) if err != nil { - return nil, err + return err } if statusCode == http.StatusOK { var history HistoryResponse if err := json.Unmarshal(response, &history); err != nil { - return nil, err + return err } // add history read scope to secret info @@ -139,33 +139,60 @@ func getHistory(client *http.Client, key string, secretInfo *SecretInfo) (*Secre } } - return secretInfo, nil + return nil } // deleteHistory try to delete a history item. The item must not exist. -func deleteHistory(client *http.Client, key string, secretInfo *SecretInfo) (*SecretInfo, error) { +func deleteHistory(client *http.Client, key string, secretInfo *SecretInfo) error { response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(SpeechHistoryWrite), http.MethodDelete, key) if err != nil { - return nil, err + return err } if statusCode >= http.StatusBadRequest && statusCode <= 499 { // check if status in response is not missing permissions ok, err := checkErrorStatus(response, MissingPermissions) if err != nil { - return nil, err + return err } // if it's missing permissions return if ok { - return secretInfo, nil + return nil } } // add history write scope to secret info secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[SpeechHistoryWrite]) - return secretInfo, nil + return nil +} + +// deleteDubbing try to delete a dubbing. The item must not exist. +func deleteDubbing(client *http.Client, key string, secretInfo *SecretInfo) error { + response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(DubbingWrite), http.MethodDelete, key) + if err != nil { + return err + } + + if statusCode >= http.StatusBadRequest && statusCode <= 499 { + // check if status in response is not missing permissions + ok, err := checkErrorStatus(response, MissingPermissions) + if err != nil { + return err + } + + // if it's missing permissions return + if ok { + return nil + } + } + + // add history write and write scope to secret info + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[DubbingWrite]) + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[DubbingRead]) + + return nil } // checkErrorStatus check if any of expected error status exist in actual API error response From de93b9b2485f94144b37bb2ad1ff1d914560a2a4 Mon Sep 17 00:00:00 2001 From: kashif khan Date: Fri, 24 Jan 2025 13:34:36 +0500 Subject: [PATCH 07/25] added dubbing read api --- .../analyzers/elevenlabs/elevenlabs.go | 7 +++++ pkg/analyzer/analyzers/elevenlabs/requests.go | 29 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index 12d91d95deba..9348b21ec7cb 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -228,6 +228,13 @@ func getResources(client *http.Client, key string, secretInfo *SecretInfo) error if err := deleteDubbing(client, key, secretInfo); err != nil { return err } + + // if dubbing write permission was not added + if !permissionExist(secretInfo.Permissions, DubbingWrite) { + if err := getDebugging(client, key, secretInfo); err != nil { + return err + } + } // voices // projects // samples diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go index 34fe2b29d126..89fac71a2158 100644 --- a/pkg/analyzer/analyzers/elevenlabs/requests.go +++ b/pkg/analyzer/analyzers/elevenlabs/requests.go @@ -40,6 +40,7 @@ var ( NotVerifiable = "api_key_not_verifiable" InvalidAPIKey = "invalid_api_key" MissingPermissions = "missing_permissions" + DubbingNotFound = "dubbing_not_found" ) // ErrorResponse is the error response for all APIs @@ -188,13 +189,39 @@ func deleteDubbing(client *http.Client, key string, secretInfo *SecretInfo) erro } } - // add history write and write scope to secret info + // add dubbing read and write scope to secret info secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[DubbingWrite]) secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[DubbingRead]) return nil } +// getDebugging try to delete a dubbing. The item must not exist. +func getDebugging(client *http.Client, key string, secretInfo *SecretInfo) error { + response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(DubbingRead), http.MethodGet, key) + if err != nil { + return err + } + + if statusCode >= http.StatusBadRequest && statusCode <= 499 { + // check if status in response is not missing permissions + ok, err := checkErrorStatus(response, MissingPermissions) + if err != nil { + return err + } + + // if it's missing permissions return + if ok { + return nil + } + } + + // add dubbing read scope to secret info + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[DubbingRead]) + + return nil +} + // checkErrorStatus check if any of expected error status exist in actual API error response func checkErrorStatus(response []byte, expectedStatuses ...string) (bool, error) { var errorResp ErrorResponse From 3cb54ca9b8a1ac940ce83fb477b86d778c5ba81f Mon Sep 17 00:00:00 2001 From: kashif khan Date: Mon, 27 Jan 2025 13:08:12 +0500 Subject: [PATCH 08/25] optimized requests and added projects api --- .../analyzers/elevenlabs/elevenlabs.go | 15 ++ pkg/analyzer/analyzers/elevenlabs/requests.go | 249 ++++++++++++++---- 2 files changed, 212 insertions(+), 52 deletions(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index 9348b21ec7cb..39b23532fd5e 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -235,8 +235,23 @@ func getResources(client *http.Client, key string, secretInfo *SecretInfo) error return err } } + // voices + if err := getVoices(client, key, secretInfo); err != nil { + return err + } + + if err := deleteVoice(client, key, secretInfo); err != nil { + return err + } // projects + if err := getProjects(client, key, secretInfo); err != nil { + return err + } + + if err := deleteProject(client, key, secretInfo); err != nil { + return err + } // samples // pronunciation dictionaries // models diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go index 89fac71a2158..adc5621dcc5e 100644 --- a/pkg/analyzer/analyzers/elevenlabs/requests.go +++ b/pkg/analyzer/analyzers/elevenlabs/requests.go @@ -18,14 +18,14 @@ var permissionToAPIMap = map[Permission]string{ AudioIsolation: "", DubbingRead: "https://api.elevenlabs.io/v1/dubbing/%s", // require dubbing id DubbingWrite: "https://api.elevenlabs.io/v1/dubbing/%s", // require dubbing id - ProjectsRead: "", - ProjectsWrite: "", + ProjectsRead: "https://api.elevenlabs.io/v1/projects", + ProjectsWrite: "https://api.elevenlabs.io/v1/projects/%s", // require project id AudioNativeRead: "", AudioNativeWrite: "", PronunciationDictionariesRead: "", PronunciationDictionariesWrite: "", - VoicesRead: "", - VoicesWrite: "", + VoicesRead: "https://api.elevenlabs.io/v1/voices", + VoicesWrite: "https://api.elevenlabs.io/v1/voices/%s", // require voice id ModelsRead: "", SpeechHistoryRead: "https://api.elevenlabs.io/v1/history", SpeechHistoryWrite: "https://api.elevenlabs.io/v1/history/%s", // require history item id @@ -37,10 +37,14 @@ var ( // not exist key fakeID = "_thou_shalt_not_exist_" // error statuses - NotVerifiable = "api_key_not_verifiable" - InvalidAPIKey = "invalid_api_key" - MissingPermissions = "missing_permissions" - DubbingNotFound = "dubbing_not_found" + NotVerifiable = "api_key_not_verifiable" + InvalidAPIKey = "invalid_api_key" + MissingPermissions = "missing_permissions" + DubbingNotFound = "dubbing_not_found" + ProjectNotFound = "project_not_found" + VoiceNotFound = "voice_does_not_exist" + InvalidSubscription = "invalid_subscription" + InternalServerError = "internal_server_error" ) // ErrorResponse is the error response for all APIs @@ -63,12 +67,29 @@ type UserResponse struct { // HistoryResponse is the /history API response type HistoryResponse struct { History []struct { - HistoryItemID string `json:"history_item_id"` - ModelID string `json:"model_id"` - VoiceID string `json:"voice_id"` + ID string `json:"history_item_id"` + ModelID string `json:"model_id"` + VoiceID string `json:"voice_id"` } `json:"history"` } +type VoicesResponse struct { + Voices []struct { + ID string `json:"voice_id"` + Name string `json:"name"` + Category string `json:"category"` + } `json:"voices"` +} + +type ProjectsResponse struct { + Projects []struct { + ID string `json:"project_id"` + Name string `json:"name"` + State string `json:"state"` + AccessLevel string `json:"access_level"` + } `json:"projects"` +} + // getAPIUrl return the API Url mapped to the permission func getAPIUrl(permission Permission) string { apiUrl := permissionToAPIMap[permission] @@ -120,7 +141,8 @@ func getHistory(client *http.Client, key string, secretInfo *SecretInfo) error { return err } - if statusCode == http.StatusOK { + switch statusCode { + case http.StatusOK: var history HistoryResponse if err := json.Unmarshal(response, &history); err != nil { @@ -132,15 +154,19 @@ func getHistory(client *http.Client, key string, secretInfo *SecretInfo) error { // map resource to secret info for _, historyItem := range history.History { secretInfo.Resources = append(secretInfo.Resources, Resource{ - ID: historyItem.HistoryItemID, + ID: historyItem.ID, Name: "", // no name Type: "History", Permission: PermissionStrings[SpeechHistoryRead], }) } - } - return nil + return nil + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking history read scope", statusCode) + } } // deleteHistory try to delete a history item. The item must not exist. @@ -150,74 +176,193 @@ func deleteHistory(client *http.Client, key string, secretInfo *SecretInfo) erro return err } - if statusCode >= http.StatusBadRequest && statusCode <= 499 { - // check if status in response is not missing permissions - ok, err := checkErrorStatus(response, MissingPermissions) - if err != nil { + switch statusCode { + case http.StatusInternalServerError: + // for some reason if we send fake id and token has the permission, the history api return 500 error instead of 404 + // issue opened in elevenlabs-docs: https://github.com/elevenlabs/elevenlabs-docs/issues/649 + return handleErrorStatus(response, PermissionStrings[SpeechHistoryWrite], secretInfo, InternalServerError) + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking history write scope", statusCode) + } +} + +// deleteDubbing try to delete a dubbing. The item must not exist. +func deleteDubbing(client *http.Client, key string, secretInfo *SecretInfo) error { + response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(DubbingWrite), http.MethodDelete, key) + if err != nil { + return err + } + + switch statusCode { + case http.StatusNotFound: + // as we send fake id, if permission is assigned to token we must get 404 dubbing not found + if err := handleErrorStatus(response, PermissionStrings[DubbingWrite], secretInfo, DubbingNotFound); err != nil { return err } - // if it's missing permissions return - if ok { - return nil - } + // add read scope of dubbing to avoid get dubbing api call + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[DubbingRead]) + + return nil + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking dubbing write scope", statusCode) } +} - // add history write scope to secret info - secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[SpeechHistoryWrite]) +// getDebugging try to get a dubbing. The item must not exist. +func getDebugging(client *http.Client, key string, secretInfo *SecretInfo) error { + response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(DubbingRead), http.MethodGet, key) + if err != nil { + return err + } - return nil + switch statusCode { + case http.StatusNotFound: + // as we send fake id, if permission is assigned to token we must get 404 dubbing not found + return handleErrorStatus(response, PermissionStrings[DubbingRead], secretInfo, DubbingNotFound) + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking dubbing read scope", statusCode) + } } -// deleteDubbing try to delete a dubbing. The item must not exist. -func deleteDubbing(client *http.Client, key string, secretInfo *SecretInfo) error { - response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(DubbingWrite), http.MethodDelete, key) +// getVoices get list of voices using the key passed and add them to secret info +func getVoices(client *http.Client, key string, secretInfo *SecretInfo) error { + response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(VoicesRead), http.MethodGet, key) if err != nil { return err } - if statusCode >= http.StatusBadRequest && statusCode <= 499 { - // check if status in response is not missing permissions - ok, err := checkErrorStatus(response, MissingPermissions) - if err != nil { + switch statusCode { + case http.StatusOK: + var voices VoicesResponse + + if err := json.Unmarshal(response, &voices); err != nil { return err } - // if it's missing permissions return - if ok { - return nil + // add voices read scope to secret info + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[VoicesRead]) + // map resource to secret info + for _, voice := range voices.Voices { + secretInfo.Resources = append(secretInfo.Resources, Resource{ + ID: voice.ID, + Name: voice.Name, + Type: "Voice", + Permission: PermissionStrings[VoicesRead], + Metadata: map[string]string{ + "category": voice.Category, + }, + }) } + + return nil + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking voice read scope", statusCode) } +} - // add dubbing read and write scope to secret info - secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[DubbingWrite]) - secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[DubbingRead]) +// deleteVoice try to delete a voice. The item must not exist. +func deleteVoice(client *http.Client, key string, secretInfo *SecretInfo) error { + response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(VoicesWrite), http.MethodDelete, key) + if err != nil { + return err + } - return nil + switch statusCode { + case http.StatusBadRequest: + // if permission was assigned to scope we should get 400 error with voice not found status + return handleErrorStatus(response, PermissionStrings[VoicesWrite], secretInfo, VoiceNotFound) + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking voice write scope", statusCode) + } } -// getDebugging try to delete a dubbing. The item must not exist. -func getDebugging(client *http.Client, key string, secretInfo *SecretInfo) error { - response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(DubbingRead), http.MethodGet, key) +// getProjects get list of projects using the key passed and add them to secret info +func getProjects(client *http.Client, key string, secretInfo *SecretInfo) error { + response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ProjectsRead), http.MethodGet, key) if err != nil { return err } - if statusCode >= http.StatusBadRequest && statusCode <= 499 { - // check if status in response is not missing permissions - ok, err := checkErrorStatus(response, MissingPermissions) - if err != nil { + switch statusCode { + case http.StatusOK: + var projects ProjectsResponse + + if err := json.Unmarshal(response, &projects); err != nil { return err } - // if it's missing permissions return - if ok { - return nil + // add project read scope to secret info + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[ProjectsRead]) + // map resource to secret info + for _, project := range projects.Projects { + secretInfo.Resources = append(secretInfo.Resources, Resource{ + ID: project.ID, + Name: project.Name, + Type: "Project", + Permission: PermissionStrings[ProjectsRead], + Metadata: map[string]string{ + "state": project.State, + "access level": project.AccessLevel, // access level of project + }, + }) } + + return nil + case http.StatusForbidden: + // if token has the permission but trail is free, projects are not accessable + return handleErrorStatus(response, PermissionStrings[ProjectsRead], secretInfo, InvalidSubscription) + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking projects read scope", statusCode) + } +} + +// deleteProject try to delete a project. The item must not exist. +func deleteProject(client *http.Client, key string, secretInfo *SecretInfo) error { + response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ProjectsWrite), http.MethodDelete, key) + if err != nil { + return err } - // add dubbing read scope to secret info - secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[DubbingRead]) + switch statusCode { + case http.StatusBadRequest: + // if permission was assigned to token we should get 400 error with project not found status + return handleErrorStatus(response, PermissionStrings[ProjectsWrite], secretInfo, ProjectNotFound) + case http.StatusForbidden: + // if token has the permission but trail is free, projects are not accessable + return handleErrorStatus(response, PermissionStrings[ProjectsWrite], secretInfo, InvalidSubscription) + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking project write scope", statusCode) + } +} + +// handleErrorStatus handle error response, check if expected error status is in the response and add require permission to secret info +// this is used in case where we expect error respones with specific status mostly in write calls +func handleErrorStatus(response []byte, permissionToAdd string, secretInfo *SecretInfo, expectedErrStatuses ...string) error { + // check if status in response is what is expected to be + ok, err := checkErrorStatus(response, expectedErrStatuses...) + if err != nil { + return err + } + + // if permission to add was passed and it was expected error status add the permission + if permissionToAdd != "" && ok { + secretInfo.Permissions = append(secretInfo.Permissions, permissionToAdd) + } return nil } From 6e2bfafc2fb1104045293c33f74c82dc8837a635 Mon Sep 17 00:00:00 2001 From: kashif khan Date: Mon, 27 Jan 2025 13:45:15 +0500 Subject: [PATCH 09/25] added pronunciation dictionaries apis --- .../analyzers/elevenlabs/elevenlabs.go | 8 +- pkg/analyzer/analyzers/elevenlabs/requests.go | 122 ++++++++++++++++-- 2 files changed, 119 insertions(+), 11 deletions(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index 39b23532fd5e..590512881aa5 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -252,8 +252,14 @@ func getResources(client *http.Client, key string, secretInfo *SecretInfo) error if err := deleteProject(client, key, secretInfo); err != nil { return err } - // samples // pronunciation dictionaries + if err := getPronunciationDictionaries(client, key, secretInfo); err != nil { + return err + } + + if err := removePronunciationDictionariesRule(client, key, secretInfo); err != nil { + return err + } // models // audio native // text to speech diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go index adc5621dcc5e..108a9d88fe2f 100644 --- a/pkg/analyzer/analyzers/elevenlabs/requests.go +++ b/pkg/analyzer/analyzers/elevenlabs/requests.go @@ -1,6 +1,7 @@ package elevenlabs import ( + "bytes" "encoding/json" "fmt" "io" @@ -22,8 +23,8 @@ var permissionToAPIMap = map[Permission]string{ ProjectsWrite: "https://api.elevenlabs.io/v1/projects/%s", // require project id AudioNativeRead: "", AudioNativeWrite: "", - PronunciationDictionariesRead: "", - PronunciationDictionariesWrite: "", + PronunciationDictionariesRead: "https://api.elevenlabs.io/v1/pronunciation-dictionaries", + PronunciationDictionariesWrite: "https://api.elevenlabs.io/v1/pronunciation-dictionaries/%s/remove-rules", // require pronunciation dictionary id VoicesRead: "https://api.elevenlabs.io/v1/voices", VoicesWrite: "https://api.elevenlabs.io/v1/voices/%s", // require voice id ModelsRead: "", @@ -37,14 +38,15 @@ var ( // not exist key fakeID = "_thou_shalt_not_exist_" // error statuses - NotVerifiable = "api_key_not_verifiable" - InvalidAPIKey = "invalid_api_key" - MissingPermissions = "missing_permissions" - DubbingNotFound = "dubbing_not_found" - ProjectNotFound = "project_not_found" - VoiceNotFound = "voice_does_not_exist" - InvalidSubscription = "invalid_subscription" - InternalServerError = "internal_server_error" + NotVerifiable = "api_key_not_verifiable" + InvalidAPIKey = "invalid_api_key" + MissingPermissions = "missing_permissions" + DubbingNotFound = "dubbing_not_found" + ProjectNotFound = "project_not_found" + VoiceNotFound = "voice_does_not_exist" + InvalidSubscription = "invalid_subscription" + PronunciationDictionaryNotFound = "pronunciation_dictionary_not_found" + InternalServerError = "internal_server_error" ) // ErrorResponse is the error response for all APIs @@ -90,6 +92,13 @@ type ProjectsResponse struct { } `json:"projects"` } +type PronunciationDictionariesResponse struct { + PronunciationDictionaries []struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"pronunciation_dictionaries"` +} + // getAPIUrl return the API Url mapped to the permission func getAPIUrl(permission Permission) string { apiUrl := permissionToAPIMap[permission] @@ -134,6 +143,40 @@ func makeElevenLabsRequest(client *http.Client, url, method, key string) ([]byte return responseBodyByte, resp.StatusCode, nil } +// makeElevenLabsPostRequest sends a POST API request to the passed URL with the given key as the API Key +// and an optional payload. It returns the response body and status code. +func makeElevenLabsPostRequest(client *http.Client, url, key string, payload []byte) ([]byte, int, error) { + // Create request with payload + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(payload)) + if err != nil { + return nil, 0, err + } + + // Add headers + req.Header.Add("xi-api-key", key) + req.Header.Add("Content-Type", "application/json") + + // Send the request + resp, err := client.Do(req) + if err != nil { + return nil, 0, err + } + + // ensure the response body is properly closed + defer func() { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() + + // read the response body + responseBodyByte, err := io.ReadAll(resp.Body) + if err != nil { + return nil, 0, err + } + + return responseBodyByte, resp.StatusCode, nil +} + // getHistory get history item using the key passed and add them to secret info func getHistory(client *http.Client, key string, secretInfo *SecretInfo) error { response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(SpeechHistoryRead), http.MethodGet, key) @@ -350,6 +393,65 @@ func deleteProject(client *http.Client, key string, secretInfo *SecretInfo) erro } } +// getPronunciationDictionaries get list of pronunciation dictionaries using the key passed and add them to secret info +func getPronunciationDictionaries(client *http.Client, key string, secretInfo *SecretInfo) error { + response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(PronunciationDictionariesRead), http.MethodGet, key) + if err != nil { + return err + } + + switch statusCode { + case http.StatusOK: + var PDs PronunciationDictionariesResponse + + if err := json.Unmarshal(response, &PDs); err != nil { + return err + } + + // add voices read scope to secret info + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[PronunciationDictionariesRead]) + // map resource to secret info + for _, pd := range PDs.PronunciationDictionaries { + secretInfo.Resources = append(secretInfo.Resources, Resource{ + ID: pd.ID, + Name: pd.Name, + Type: "Pronunciation Dictionary", + Permission: PermissionStrings[PronunciationDictionariesRead], + }) + } + + return nil + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking pronunciation dictionaries read scope", statusCode) + } +} + +// removePronunciationDictionariesRule try to remove a rule from pronunciation dictionaries. The item must not exist. +func removePronunciationDictionariesRule(client *http.Client, key string, secretInfo *SecretInfo) error { + // send empty list of rule strings + payload := map[string]interface{}{ + "rule_strings": []string{""}, + } + + payloadBytes, _ := json.Marshal(payload) + response, statusCode, err := makeElevenLabsPostRequest(client, getAPIUrl(PronunciationDictionariesWrite), key, payloadBytes) + if err != nil { + return err + } + + switch statusCode { + case http.StatusNotFound: + // if permission was assigned to token we should get 404 error with pronunciation_dictionary_not_found status + return handleErrorStatus(response, PermissionStrings[PronunciationDictionariesWrite], secretInfo, PronunciationDictionaryNotFound) + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking pronunciation dictionary write scope", statusCode) + } +} + // handleErrorStatus handle error response, check if expected error status is in the response and add require permission to secret info // this is used in case where we expect error respones with specific status mostly in write calls func handleErrorStatus(response []byte, permissionToAdd string, secretInfo *SecretInfo, expectedErrStatuses ...string) error { From 0c79411a3ab2887a6267cf8d27af6bd1fed82ba0 Mon Sep 17 00:00:00 2001 From: kashif khan Date: Mon, 27 Jan 2025 13:47:02 +0500 Subject: [PATCH 10/25] fixed linter --- pkg/analyzer/analyzers/elevenlabs/requests.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go index 108a9d88fe2f..8b448a7d2440 100644 --- a/pkg/analyzer/analyzers/elevenlabs/requests.go +++ b/pkg/analyzer/analyzers/elevenlabs/requests.go @@ -363,7 +363,7 @@ func getProjects(client *http.Client, key string, secretInfo *SecretInfo) error return nil case http.StatusForbidden: - // if token has the permission but trail is free, projects are not accessable + // if token has the permission but trail is free, projects are not accessible return handleErrorStatus(response, PermissionStrings[ProjectsRead], secretInfo, InvalidSubscription) case http.StatusUnauthorized: return handleErrorStatus(response, "", secretInfo, MissingPermissions) @@ -384,7 +384,7 @@ func deleteProject(client *http.Client, key string, secretInfo *SecretInfo) erro // if permission was assigned to token we should get 400 error with project not found status return handleErrorStatus(response, PermissionStrings[ProjectsWrite], secretInfo, ProjectNotFound) case http.StatusForbidden: - // if token has the permission but trail is free, projects are not accessable + // if token has the permission but trail is free, projects are not accessible return handleErrorStatus(response, PermissionStrings[ProjectsWrite], secretInfo, InvalidSubscription) case http.StatusUnauthorized: return handleErrorStatus(response, "", secretInfo, MissingPermissions) @@ -453,7 +453,7 @@ func removePronunciationDictionariesRule(client *http.Client, key string, secret } // handleErrorStatus handle error response, check if expected error status is in the response and add require permission to secret info -// this is used in case where we expect error respones with specific status mostly in write calls +// this is used in case where we expect error response with specific status mostly in write calls func handleErrorStatus(response []byte, permissionToAdd string, secretInfo *SecretInfo, expectedErrStatuses ...string) error { // check if status in response is what is expected to be ok, err := checkErrorStatus(response, expectedErrStatuses...) From 96dc34becf2b859f110b5489de81175d4afbbe85 Mon Sep 17 00:00:00 2001 From: kashif khan Date: Mon, 27 Jan 2025 15:47:27 +0500 Subject: [PATCH 11/25] added models,audionative,workspace apis --- .../analyzers/elevenlabs/elevenlabs.go | 16 +++ .../analyzers/elevenlabs/permissions.go | 9 +- .../analyzers/elevenlabs/permissions.yaml | 2 +- pkg/analyzer/analyzers/elevenlabs/requests.go | 123 ++++++++++++++++-- 4 files changed, 138 insertions(+), 12 deletions(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index 590512881aa5..d5db508ffc73 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -244,6 +244,7 @@ func getResources(client *http.Client, key string, secretInfo *SecretInfo) error if err := deleteVoice(client, key, secretInfo); err != nil { return err } + // projects if err := getProjects(client, key, secretInfo); err != nil { return err @@ -252,6 +253,7 @@ func getResources(client *http.Client, key string, secretInfo *SecretInfo) error if err := deleteProject(client, key, secretInfo); err != nil { return err } + // pronunciation dictionaries if err := getPronunciationDictionaries(client, key, secretInfo); err != nil { return err @@ -260,8 +262,22 @@ func getResources(client *http.Client, key string, secretInfo *SecretInfo) error if err := removePronunciationDictionariesRule(client, key, secretInfo); err != nil { return err } + // models + if err := getModels(client, key, secretInfo); err != nil { + return err + } + // audio native + if err := updateAudioNativeProject(client, key, secretInfo); err != nil { + return err + } + + // workspace + if err := deleteInviteFromWorkspace(client, key, secretInfo); err != nil { + return err + } + // text to speech // voice changer // audio isolation diff --git a/pkg/analyzer/analyzers/elevenlabs/permissions.go b/pkg/analyzer/analyzers/elevenlabs/permissions.go index 87af32ce6021..816e79f18bd4 100644 --- a/pkg/analyzer/analyzers/elevenlabs/permissions.go +++ b/pkg/analyzer/analyzers/elevenlabs/permissions.go @@ -25,6 +25,7 @@ const ( SpeechHistoryRead Permission = iota SpeechHistoryWrite Permission = iota UserRead Permission = iota + WorkspaceRead Permission = iota WorkspaceWrite Permission = iota ) @@ -48,6 +49,7 @@ var ( SpeechHistoryRead: "speech_history_read", SpeechHistoryWrite: "speech_history_write", UserRead: "user_read", + WorkspaceRead: "workspace_read", WorkspaceWrite: "workspace_write", } @@ -70,6 +72,7 @@ var ( "speech_history_read": SpeechHistoryRead, "speech_history_write": SpeechHistoryWrite, "user_read": UserRead, + "workspace_read": WorkspaceRead, "workspace_write": WorkspaceWrite, } @@ -92,7 +95,8 @@ var ( SpeechHistoryRead: 16, SpeechHistoryWrite: 17, UserRead: 18, - WorkspaceWrite: 19, + WorkspaceRead: 19, + WorkspaceWrite: 20, } IdToPermission = map[int]Permission{ @@ -114,7 +118,8 @@ var ( 16: SpeechHistoryRead, 17: SpeechHistoryWrite, 18: UserRead, - 19: WorkspaceWrite, + 19: WorkspaceRead, + 20: WorkspaceWrite, } ) diff --git a/pkg/analyzer/analyzers/elevenlabs/permissions.yaml b/pkg/analyzer/analyzers/elevenlabs/permissions.yaml index ec08726c83f6..2fdbd0b27945 100644 --- a/pkg/analyzer/analyzers/elevenlabs/permissions.yaml +++ b/pkg/analyzer/analyzers/elevenlabs/permissions.yaml @@ -20,5 +20,5 @@ permissions: - speech_history_write - user_read # - user_write - # - workspace_read + - workspace_read - workspace_write diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go index 8b448a7d2440..fa1c2f66a8a4 100644 --- a/pkg/analyzer/analyzers/elevenlabs/requests.go +++ b/pkg/analyzer/analyzers/elevenlabs/requests.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "mime/multipart" "net/http" "slices" "strings" @@ -20,18 +21,17 @@ var permissionToAPIMap = map[Permission]string{ DubbingRead: "https://api.elevenlabs.io/v1/dubbing/%s", // require dubbing id DubbingWrite: "https://api.elevenlabs.io/v1/dubbing/%s", // require dubbing id ProjectsRead: "https://api.elevenlabs.io/v1/projects", - ProjectsWrite: "https://api.elevenlabs.io/v1/projects/%s", // require project id - AudioNativeRead: "", - AudioNativeWrite: "", + ProjectsWrite: "https://api.elevenlabs.io/v1/projects/%s", // require project id + AudioNativeWrite: "https://api.elevenlabs.io/v1/audio-native/%s/content", // require project id PronunciationDictionariesRead: "https://api.elevenlabs.io/v1/pronunciation-dictionaries", PronunciationDictionariesWrite: "https://api.elevenlabs.io/v1/pronunciation-dictionaries/%s/remove-rules", // require pronunciation dictionary id VoicesRead: "https://api.elevenlabs.io/v1/voices", VoicesWrite: "https://api.elevenlabs.io/v1/voices/%s", // require voice id - ModelsRead: "", + ModelsRead: "https://api.elevenlabs.io/v1/models", SpeechHistoryRead: "https://api.elevenlabs.io/v1/history", SpeechHistoryWrite: "https://api.elevenlabs.io/v1/history/%s", // require history item id UserRead: "https://api.elevenlabs.io/v1/user", - WorkspaceWrite: "", + WorkspaceWrite: "https://api.elevenlabs.io/v1/workspace/invites", } var ( @@ -47,6 +47,7 @@ var ( InvalidSubscription = "invalid_subscription" PronunciationDictionaryNotFound = "pronunciation_dictionary_not_found" InternalServerError = "internal_server_error" + InvalidProjectID = "invalid_project_id" ) // ErrorResponse is the error response for all APIs @@ -99,6 +100,11 @@ type PronunciationDictionariesResponse struct { } `json:"pronunciation_dictionaries"` } +type ModelsResponse struct { + ID string `json:"model_id"` + Name string `json:"name"` +} + // getAPIUrl return the API Url mapped to the permission func getAPIUrl(permission Permission) string { apiUrl := permissionToAPIMap[permission] @@ -143,9 +149,9 @@ func makeElevenLabsRequest(client *http.Client, url, method, key string) ([]byte return responseBodyByte, resp.StatusCode, nil } -// makeElevenLabsPostRequest sends a POST API request to the passed URL with the given key as the API Key +// makeElevenLabsRequestWithPayload sends a POST/PATCH API request to the passed URL with the given key as the API Key // and an optional payload. It returns the response body and status code. -func makeElevenLabsPostRequest(client *http.Client, url, key string, payload []byte) ([]byte, int, error) { +func makeElevenLabsRequestWithPayload(client *http.Client, url, contentType, key string, payload []byte) ([]byte, int, error) { // Create request with payload req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(payload)) if err != nil { @@ -154,7 +160,7 @@ func makeElevenLabsPostRequest(client *http.Client, url, key string, payload []b // Add headers req.Header.Add("xi-api-key", key) - req.Header.Add("Content-Type", "application/json") + req.Header.Add("Content-Type", contentType) // Send the request resp, err := client.Do(req) @@ -436,7 +442,7 @@ func removePronunciationDictionariesRule(client *http.Client, key string, secret } payloadBytes, _ := json.Marshal(payload) - response, statusCode, err := makeElevenLabsPostRequest(client, getAPIUrl(PronunciationDictionariesWrite), key, payloadBytes) + response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(PronunciationDictionariesWrite), "application/json", key, payloadBytes) if err != nil { return err } @@ -452,6 +458,105 @@ func removePronunciationDictionariesRule(client *http.Client, key string, secret } } +// getModels list models using the key passed and add them to secret info +func getModels(client *http.Client, key string, secretInfo *SecretInfo) error { + response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ModelsRead), http.MethodGet, key) + if err != nil { + return err + } + + switch statusCode { + case http.StatusOK: + var models []ModelsResponse + + if err := json.Unmarshal(response, &models); err != nil { + return err + } + + // add models read scope to secret info + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[ModelsRead]) + // map resource to secret info + for _, model := range models { + secretInfo.Resources = append(secretInfo.Resources, Resource{ + ID: model.ID, + Name: model.Name, + Type: "Model", + Permission: PermissionStrings[ModelsRead], + }) + } + + return nil + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking models read scope", statusCode) + } +} + +// updateAudioNativeProject try to update a project content. The item must not exist. +func updateAudioNativeProject(client *http.Client, key string, secretInfo *SecretInfo) error { + // create a buffer to hold the multipart form data + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + // add required fields to multipart form body + _ = writer.WriteField("auto_convert", "false") + _ = writer.WriteField("auto_publish", "false") + // close the writer + _ = writer.Close() + + response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(AudioNativeWrite), writer.FormDataContentType(), key, body.Bytes()) + if err != nil { + return err + } + + switch statusCode { + case http.StatusBadRequest: + // if the permission is assigned to token, the api should return 400 with invalid project id + if err := handleErrorStatus(response, PermissionStrings[AudioNativeWrite], secretInfo, InvalidProjectID); err != nil { + return err + } + + // add read permission as no separate API exist to check read audio native permission + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[AudioNativeRead]) + return nil + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking audio native write scope", statusCode) + } +} + +// deleteInviteFromWorkspace try to remove a invite from workspace. The item must not exist. +func deleteInviteFromWorkspace(client *http.Client, key string, secretInfo *SecretInfo) error { + // send fake email in payload + payload := map[string]interface{}{ + "email": fakeID + "@example.com", + } + + payloadBytes, _ := json.Marshal(payload) + response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(PronunciationDictionariesWrite), "application/json", key, payloadBytes) + if err != nil { + return err + } + + switch statusCode { + case http.StatusInternalServerError: + // for some reason if we send fake email and token has the permission, the workspace invite api return 500 error instead of 404 + if err := handleErrorStatus(response, PermissionStrings[WorkspaceWrite], secretInfo, InternalServerError); err != nil { + return err + } + + // add read permission as no separate API exist to check workspace read permission + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[WorkspaceRead]) + return nil + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking workspace write scope", statusCode) + } +} + // handleErrorStatus handle error response, check if expected error status is in the response and add require permission to secret info // this is used in case where we expect error response with specific status mostly in write calls func handleErrorStatus(response []byte, permissionToAdd string, secretInfo *SecretInfo, expectedErrStatuses ...string) error { From cb957aa39eff6b0d5da030926393fc711742a590 Mon Sep 17 00:00:00 2001 From: kashif khan Date: Mon, 27 Jan 2025 19:24:17 +0500 Subject: [PATCH 12/25] added some last apis --- "\\" | 1 + .../analyzers/elevenlabs/elevenlabs.go | 119 +++++++--- .../analyzers/elevenlabs/permissions.go | 73 +++--- .../analyzers/elevenlabs/permissions.yaml | 2 +- pkg/analyzer/analyzers/elevenlabs/requests.go | 207 +++++++++++++++++- 5 files changed, 317 insertions(+), 85 deletions(-) create mode 100644 "\\" diff --git "a/\\" "b/\\" new file mode 100644 index 000000000000..6bc32aaba1db --- /dev/null +++ "b/\\" @@ -0,0 +1 @@ +Date: 2025-01-27T17:02:25+05:00, Method: GET, Path: /v1/user, Status: 401 diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index d5db508ffc73..8e513fb02820 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -47,6 +47,7 @@ type Resource struct { Type string Metadata map[string]string Permission string + Parent *Resource } func (a Analyzer) Type() analyzers.AnalyzerType { @@ -76,18 +77,19 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) { var secretInfo = &SecretInfo{} // validate the key and get user information - valid, err := validateKey(client, key, secretInfo) - if err != nil { + if err := validateKey(client, key, secretInfo); err != nil { return nil, err } - if !valid { - return nil, errors.New("key is not valid") + if secretInfo.Valid { + // Get resources + if err := getResources(client, key, secretInfo); err != nil { + return nil, err + } } - // Get resources - if err := getResources(client, key, secretInfo); err != nil { - return nil, nil + if err := getUnboundedResources(client, key, secretInfo); err != nil { + return nil, err } return secretInfo, nil @@ -132,22 +134,46 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult { // extract information from resource to create bindings and append to result bindings for _, resource := range info.Resources { - binding := analyzers.Binding{ - Resource: analyzers.Resource{ + // if resource has permission it is binded resource + if resource.Permission != "" { + binding := analyzers.Binding{ + Resource: analyzers.Resource{ + Name: resource.Name, + FullyQualifiedName: resource.ID, + Type: resource.Type, + }, + Permission: analyzers.Permission{ + Value: resource.Permission, + }, + } + + for key, value := range resource.Metadata { + binding.Resource.Metadata[key] = value + } + + result.Bindings = append(result.Bindings, binding) + } else { + // if resource is missing permission it is an unbounded resource + unboundedResource := analyzers.Resource{ Name: resource.Name, FullyQualifiedName: resource.ID, Type: resource.Type, - }, - Permission: analyzers.Permission{ - Value: resource.Permission, - }, - } + } - for key, value := range resource.Metadata { - binding.Resource.Metadata[key] = value - } + for key, value := range resource.Metadata { + unboundedResource.Metadata[key] = value + } + + if resource.Parent != nil { + unboundedResource.Parent = &analyzers.Resource{ + Name: resource.Parent.Name, + FullyQualifiedName: resource.Parent.ID, + Type: resource.Parent.Type, + } + } - result.Bindings = append(result.Bindings, binding) + result.UnboundedResources = append(result.UnboundedResources, unboundedResource) + } } result.Metadata["Valid_Key"] = info.Valid @@ -156,17 +182,18 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult { } // validateKey check if the key is valid and get the user information if it's valid -func validateKey(client *http.Client, key string, secretInfo *SecretInfo) (bool, error) { +func validateKey(client *http.Client, key string, secretInfo *SecretInfo) error { response, statusCode, err := makeElevenLabsRequest(client, permissionToAPIMap[UserRead], http.MethodGet, key) if err != nil { - return false, err + return err } - if statusCode == http.StatusOK { + switch statusCode { + case http.StatusOK: var user UserResponse if err := json.Unmarshal(response, &user); err != nil { - return false, err + return err } // map info to secretInfo @@ -187,21 +214,26 @@ func validateKey(client *http.Client, key string, secretInfo *SecretInfo) (bool, Permission: PermissionStrings[UserRead], }) - return true, nil - } else if statusCode >= http.StatusBadRequest && statusCode <= 499 { - // check if api key is invalid or not verifiable, return false - ok, err := checkErrorStatus(response, InvalidAPIKey, NotVerifiable) - if err != nil { - return false, err + return nil + case http.StatusUnauthorized: + var errorResp ErrorResponse + + if err := json.Unmarshal(response, &errorResp); err != nil { + return err } - if ok { - return false, nil + if errorResp.Detail.Status == InvalidAPIKey || errorResp.Detail.Status == NotVerifiable { + return errors.New("invalid api key") + } else if errorResp.Detail.Status == MissingPermissions { + // key is missing user read permissions but is valid + secretInfo.Valid = true + color.Yellow("\n[!] API Key missing user read permissions") } - } - // if no expected status code was detected - return false, fmt.Errorf("unexpected status code: %d", statusCode) + return nil + default: + return fmt.Errorf("unexpected status code: %d", statusCode) + } } /* @@ -279,8 +311,29 @@ func getResources(client *http.Client, key string, secretInfo *SecretInfo) error } // text to speech + if err := textToSpeech(client, key, secretInfo); err != nil { + return err + } + // voice changer + if err := speechToSpeech(client, key, secretInfo); err != nil { + return err + } + // audio isolation + if err := audioIsolation(client, key, secretInfo); err != nil { + return err + } + + return nil +} + +// getUnboundedResources gather resources which can be accessed without any permission +func getUnboundedResources(client *http.Client, key string, secretInfo *SecretInfo) error { + // each agent can have a conversations which we get inside this function + if err := getAgents(client, key, secretInfo); err != nil { + return err + } return nil } diff --git a/pkg/analyzer/analyzers/elevenlabs/permissions.go b/pkg/analyzer/analyzers/elevenlabs/permissions.go index 816e79f18bd4..e08445987017 100644 --- a/pkg/analyzer/analyzers/elevenlabs/permissions.go +++ b/pkg/analyzer/analyzers/elevenlabs/permissions.go @@ -9,7 +9,6 @@ const ( Invalid Permission = iota TextToSpeech Permission = iota SpeechToSpeech Permission = iota - SoundGeneration Permission = iota AudioIsolation Permission = iota DubbingRead Permission = iota DubbingWrite Permission = iota @@ -33,7 +32,6 @@ var ( PermissionStrings = map[Permission]string{ TextToSpeech: "text_to_speech", SpeechToSpeech: "speech_to_speech", - SoundGeneration: "sound_generation", AudioIsolation: "audio_isolation", DubbingRead: "dubbing_read", DubbingWrite: "dubbing_write", @@ -56,7 +54,6 @@ var ( StringToPermission = map[string]Permission{ "text_to_speech": TextToSpeech, "speech_to_speech": SpeechToSpeech, - "sound_generation": SoundGeneration, "audio_isolation": AudioIsolation, "dubbing_read": DubbingRead, "dubbing_write": DubbingWrite, @@ -79,47 +76,45 @@ var ( PermissionIDs = map[Permission]int{ TextToSpeech: 1, SpeechToSpeech: 2, - SoundGeneration: 3, - AudioIsolation: 4, - DubbingRead: 5, - DubbingWrite: 6, - ProjectsRead: 7, - ProjectsWrite: 8, - AudioNativeRead: 9, - AudioNativeWrite: 10, - PronunciationDictionariesRead: 11, - PronunciationDictionariesWrite: 12, - VoicesRead: 13, - VoicesWrite: 14, - ModelsRead: 15, - SpeechHistoryRead: 16, - SpeechHistoryWrite: 17, - UserRead: 18, - WorkspaceRead: 19, - WorkspaceWrite: 20, + AudioIsolation: 3, + DubbingRead: 4, + DubbingWrite: 5, + ProjectsRead: 6, + ProjectsWrite: 7, + AudioNativeRead: 8, + AudioNativeWrite: 9, + PronunciationDictionariesRead: 10, + PronunciationDictionariesWrite: 11, + VoicesRead: 12, + VoicesWrite: 13, + ModelsRead: 14, + SpeechHistoryRead: 15, + SpeechHistoryWrite: 16, + UserRead: 17, + WorkspaceRead: 18, + WorkspaceWrite: 19, } IdToPermission = map[int]Permission{ 1: TextToSpeech, 2: SpeechToSpeech, - 3: SoundGeneration, - 4: AudioIsolation, - 5: DubbingRead, - 6: DubbingWrite, - 7: ProjectsRead, - 8: ProjectsWrite, - 9: AudioNativeRead, - 10: AudioNativeWrite, - 11: PronunciationDictionariesRead, - 12: PronunciationDictionariesWrite, - 13: VoicesRead, - 14: VoicesWrite, - 15: ModelsRead, - 16: SpeechHistoryRead, - 17: SpeechHistoryWrite, - 18: UserRead, - 19: WorkspaceRead, - 20: WorkspaceWrite, + 3: AudioIsolation, + 4: DubbingRead, + 5: DubbingWrite, + 6: ProjectsRead, + 7: ProjectsWrite, + 8: AudioNativeRead, + 9: AudioNativeWrite, + 10: PronunciationDictionariesRead, + 11: PronunciationDictionariesWrite, + 12: VoicesRead, + 13: VoicesWrite, + 14: ModelsRead, + 15: SpeechHistoryRead, + 16: SpeechHistoryWrite, + 17: UserRead, + 18: WorkspaceRead, + 19: WorkspaceWrite, } ) diff --git a/pkg/analyzer/analyzers/elevenlabs/permissions.yaml b/pkg/analyzer/analyzers/elevenlabs/permissions.yaml index 2fdbd0b27945..05977065582f 100644 --- a/pkg/analyzer/analyzers/elevenlabs/permissions.yaml +++ b/pkg/analyzer/analyzers/elevenlabs/permissions.yaml @@ -1,7 +1,7 @@ permissions: - text_to_speech - speech_to_speech - - sound_generation + # - sound_generation - audio_isolation # - voice_generation - dubbing_read diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go index fa1c2f66a8a4..e7f01fe1e337 100644 --- a/pkg/analyzer/analyzers/elevenlabs/requests.go +++ b/pkg/analyzer/analyzers/elevenlabs/requests.go @@ -14,10 +14,9 @@ import ( // permissionToAPIMap contain the API endpoints for each scope/permission // api docs: https://elevenlabs.io/docs/api-reference/introduction var permissionToAPIMap = map[Permission]string{ - TextToSpeech: "https://api.elevenlabs.io/v1/text-to-speech/%s", // require voice id - SpeechToSpeech: "", - SoundGeneration: "", - AudioIsolation: "", + TextToSpeech: "https://api.elevenlabs.io/v1/text-to-speech/%s", // require voice id + SpeechToSpeech: "https://api.elevenlabs.io/v1/speech-to-speech/%s", // require voice id + AudioIsolation: "https://api.elevenlabs.io/v1/audio-isolation", DubbingRead: "https://api.elevenlabs.io/v1/dubbing/%s", // require dubbing id DubbingWrite: "https://api.elevenlabs.io/v1/dubbing/%s", // require dubbing id ProjectsRead: "https://api.elevenlabs.io/v1/projects", @@ -43,11 +42,14 @@ var ( MissingPermissions = "missing_permissions" DubbingNotFound = "dubbing_not_found" ProjectNotFound = "project_not_found" - VoiceNotFound = "voice_does_not_exist" + VoiceDoesNotExist = "voice_does_not_exist" InvalidSubscription = "invalid_subscription" PronunciationDictionaryNotFound = "pronunciation_dictionary_not_found" InternalServerError = "internal_server_error" InvalidProjectID = "invalid_project_id" + ModelNotFound = "model_not_found" + VoiceNotFound = "voice_not_found" + InvalidContent = "invalid_content" ) // ErrorResponse is the error response for all APIs @@ -105,6 +107,22 @@ type ModelsResponse struct { Name string `json:"name"` } +type AgentsResponse struct { + Agents []struct { + ID string `json:"agent_id"` + Name string `json:"name"` + AccessLevel string `json:"access_level"` + } `json:"agents"` +} + +type ConversationResponse struct { + Conversations []struct { + AgentID string `json:"agent_id"` + ID string `json:"conversation_id"` + Status string `json:"status"` + } +} + // getAPIUrl return the API Url mapped to the permission func getAPIUrl(permission Permission) string { apiUrl := permissionToAPIMap[permission] @@ -118,7 +136,7 @@ func getAPIUrl(permission Permission) string { // makeElevenLabsRequest send the API request to passed url with passed key as API Key and return response body and status code func makeElevenLabsRequest(client *http.Client, url, method, key string) ([]byte, int, error) { // create request - req, err := http.NewRequest(method, url, nil) + req, err := http.NewRequest(method, url, http.NoBody) if err != nil { return nil, 0, err } @@ -151,9 +169,9 @@ func makeElevenLabsRequest(client *http.Client, url, method, key string) ([]byte // makeElevenLabsRequestWithPayload sends a POST/PATCH API request to the passed URL with the given key as the API Key // and an optional payload. It returns the response body and status code. -func makeElevenLabsRequestWithPayload(client *http.Client, url, contentType, key string, payload []byte) ([]byte, int, error) { +func makeElevenLabsRequestWithPayload(client *http.Client, url, method, contentType, key string, payload []byte) ([]byte, int, error) { // Create request with payload - req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(payload)) + req, err := http.NewRequest(method, url, bytes.NewBuffer(payload)) if err != nil { return nil, 0, err } @@ -328,7 +346,7 @@ func deleteVoice(client *http.Client, key string, secretInfo *SecretInfo) error switch statusCode { case http.StatusBadRequest: // if permission was assigned to scope we should get 400 error with voice not found status - return handleErrorStatus(response, PermissionStrings[VoicesWrite], secretInfo, VoiceNotFound) + return handleErrorStatus(response, PermissionStrings[VoicesWrite], secretInfo, VoiceDoesNotExist) case http.StatusUnauthorized: return handleErrorStatus(response, "", secretInfo, MissingPermissions) default: @@ -442,7 +460,8 @@ func removePronunciationDictionariesRule(client *http.Client, key string, secret } payloadBytes, _ := json.Marshal(payload) - response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(PronunciationDictionariesWrite), "application/json", key, payloadBytes) + response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(PronunciationDictionariesWrite), http.MethodPost, + "application/json", key, payloadBytes) if err != nil { return err } @@ -505,7 +524,8 @@ func updateAudioNativeProject(client *http.Client, key string, secretInfo *Secre // close the writer _ = writer.Close() - response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(AudioNativeWrite), writer.FormDataContentType(), key, body.Bytes()) + response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(AudioNativeWrite), http.MethodPost, + writer.FormDataContentType(), key, body.Bytes()) if err != nil { return err } @@ -535,7 +555,8 @@ func deleteInviteFromWorkspace(client *http.Client, key string, secretInfo *Secr } payloadBytes, _ := json.Marshal(payload) - response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(PronunciationDictionariesWrite), "application/json", key, payloadBytes) + response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(WorkspaceWrite), http.MethodDelete, + "application/json", key, payloadBytes) if err != nil { return err } @@ -557,6 +578,168 @@ func deleteInviteFromWorkspace(client *http.Client, key string, secretInfo *Secr } } +// textToSpeech try to convert text to speech. The model id and voice id is fake so it actually never happens. +func textToSpeech(client *http.Client, key string, secretInfo *SecretInfo) error { + // send fake model id in payload + payload := map[string]interface{}{ + "text": "This is trufflehog trying to check text to speech permission of the token", + "model_id": fakeID, + } + + payloadBytes, _ := json.Marshal(payload) + response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(TextToSpeech), http.MethodPost, + "application/json", key, payloadBytes) + if err != nil { + return err + } + + switch statusCode { + case http.StatusBadRequest: + // if permission is assigned to token, error status will be either model not found or voice not found as we sent both fake ;) + return handleErrorStatus(response, PermissionStrings[TextToSpeech], secretInfo, ModelNotFound, VoiceNotFound) + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking text to speech scope", statusCode) + } +} + +// speechToSpeech try to change a voice in speech. The model id and voice id is fake so it actually never happens. +func speechToSpeech(client *http.Client, key string, secretInfo *SecretInfo) error { + // create a buffer to hold the multipart form data + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + // add required fields to multipart form body + _ = writer.WriteField("model_id", fakeID) + _ = writer.WriteField("seed", "1") + _ = writer.WriteField("remove_background_noise", "false") + audio, _ := writer.CreateFormFile("audio", "") + audio.Write([]byte("This is example fake audio for api call")) + // close the writer + _ = writer.Close() + + response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(SpeechToSpeech), http.MethodPost, + writer.FormDataContentType(), key, body.Bytes()) + if err != nil { + return err + } + + switch statusCode { + case http.StatusBadRequest: + return handleErrorStatus(response, PermissionStrings[SpeechToSpeech], secretInfo, InvalidContent) + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking speech to speech scope", statusCode) + } +} + +// audioIsolation try to remove background noise from a voice. The file will be corrupted so it should return an error. +func audioIsolation(client *http.Client, key string, secretInfo *SecretInfo) error { + // create a buffer to hold the multipart form data + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + audio, _ := writer.CreateFormFile("audio", "") + audio.Write([]byte("This is example fake audio for api call")) + // close the writer + _ = writer.Close() + + response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(AudioIsolation), http.MethodPost, + writer.FormDataContentType(), key, body.Bytes()) + if err != nil { + return err + } + + switch statusCode { + case http.StatusBadRequest: + return handleErrorStatus(response, PermissionStrings[AudioIsolation], secretInfo, InvalidContent) + case http.StatusUnauthorized: + return handleErrorStatus(response, "", secretInfo, MissingPermissions) + default: + return fmt.Errorf("unexpected status code: %d while checking audio isolation speech scope", statusCode) + } +} + +/* +getAgents get all user agents which are not bound with any permission +call APIs in pattern: agents->conversation +*/ +func getAgents(client *http.Client, key string, secretInfo *SecretInfo) error { + response, statusCode, err := makeElevenLabsRequest(client, "https://api.elevenlabs.io/v1/convai/agents", http.MethodGet, key) + if err != nil { + return err + } + + switch statusCode { + case http.StatusOK: + var agents AgentsResponse + + if err := json.Unmarshal(response, &agents); err != nil { + return err + } + + // map resource to secret info + for _, agent := range agents.Agents { + resource := Resource{ + ID: agent.ID, + Name: agent.Name, + Type: "Agent", + Permission: "", // not binded with any permission + Metadata: map[string]string{ + "access level": agent.AccessLevel, + }, + } + secretInfo.Resources = append(secretInfo.Resources, resource) + // get agent conversations + if err := getConversation(client, key, agent.ID, &resource, secretInfo); err != nil { + return err + } + } + + return nil + default: + return fmt.Errorf("unexpected status code: %d while checking models read scope", statusCode) + } +} + +// getConversation list all agent conversations using the key and agentID passed and add them to secret info +func getConversation(client *http.Client, key, agentID string, parent *Resource, secretInfo *SecretInfo) error { + apiUrl := fmt.Sprintf("https://api.elevenlabs.io/v1/convai/conversations?agent_id=%s", agentID) + response, statusCode, err := makeElevenLabsRequest(client, apiUrl, http.MethodGet, key) + if err != nil { + return err + } + + switch statusCode { + case http.StatusOK: + var conversations ConversationResponse + + if err := json.Unmarshal(response, &conversations); err != nil { + return err + } + + // map resource to secret info + for _, conversation := range conversations.Conversations { + secretInfo.Resources = append(secretInfo.Resources, Resource{ + ID: conversation.ID, + Name: "", // no name + Type: "Conversation", + Permission: "", // not binded with any permission + Metadata: map[string]string{ + "status": conversation.Status, + }, + Parent: parent, + }) + } + + return nil + default: + return fmt.Errorf("unexpected status code: %d while checking models read scope", statusCode) + } +} + // handleErrorStatus handle error response, check if expected error status is in the response and add require permission to secret info // this is used in case where we expect error response with specific status mostly in write calls func handleErrorStatus(response []byte, permissionToAdd string, secretInfo *SecretInfo, expectedErrStatuses ...string) error { From 58f1736a56f3295390fa0236142fbb77c2e554ce Mon Sep 17 00:00:00 2001 From: Kashif Khan <70996046+kashifkhan0771@users.noreply.github.com> Date: Mon, 27 Jan 2025 19:25:36 +0500 Subject: [PATCH 13/25] Deleted \ --- "\\" | 1 - 1 file changed, 1 deletion(-) delete mode 100644 "\\" diff --git "a/\\" "b/\\" deleted file mode 100644 index 6bc32aaba1db..000000000000 --- "a/\\" +++ /dev/null @@ -1 +0,0 @@ -Date: 2025-01-27T17:02:25+05:00, Method: GET, Path: /v1/user, Status: 401 From 5fd970593ad7a94d82ab0a9db2cb3c39f31467d4 Mon Sep 17 00:00:00 2001 From: kashif khan Date: Mon, 27 Jan 2025 19:32:52 +0500 Subject: [PATCH 14/25] fixed linter --- pkg/analyzer/analyzers/elevenlabs/requests.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go index e7f01fe1e337..1e118eb4aa90 100644 --- a/pkg/analyzer/analyzers/elevenlabs/requests.go +++ b/pkg/analyzer/analyzers/elevenlabs/requests.go @@ -615,7 +615,7 @@ func speechToSpeech(client *http.Client, key string, secretInfo *SecretInfo) err _ = writer.WriteField("seed", "1") _ = writer.WriteField("remove_background_noise", "false") audio, _ := writer.CreateFormFile("audio", "") - audio.Write([]byte("This is example fake audio for api call")) + _, _ = audio.Write([]byte("This is example fake audio for api call")) // close the writer _ = writer.Close() @@ -642,7 +642,7 @@ func audioIsolation(client *http.Client, key string, secretInfo *SecretInfo) err writer := multipart.NewWriter(body) audio, _ := writer.CreateFormFile("audio", "") - audio.Write([]byte("This is example fake audio for api call")) + _, _ = audio.Write([]byte("This is example fake audio for api call")) // close the writer _ = writer.Close() From b36caf370a037fc4eda43686a29cff2e72287034 Mon Sep 17 00:00:00 2001 From: kashif khan Date: Tue, 28 Jan 2025 12:43:44 +0500 Subject: [PATCH 15/25] few more enhancements and test cases added --- .../analyzers/elevenlabs/elevenlabs.go | 16 +- .../analyzers/elevenlabs/elevenlabs_test.go | 102 ++++ pkg/analyzer/analyzers/elevenlabs/requests.go | 11 +- .../analyzers/elevenlabs/result_output.json | 527 ++++++++++++++++++ pkg/detectors/elevenlabs/v1/elevenlabs.go | 6 + pkg/detectors/elevenlabs/v2/elevenlabs.go | 6 + .../v2/elevenlabs_integration_test.go | 4 +- 7 files changed, 658 insertions(+), 14 deletions(-) create mode 100644 pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go create mode 100644 pkg/analyzer/analyzers/elevenlabs/result_output.json diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index 8e513fb02820..41b5d5bb8d3d 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -129,11 +129,15 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult { result := analyzers.AnalyzerResult{ AnalyzerType: analyzers.AnalyzerTypeElevenLabs, Metadata: map[string]any{}, - Bindings: make([]analyzers.Binding, len(info.Permissions)), + Bindings: make([]analyzers.Binding, 0), } // extract information from resource to create bindings and append to result bindings for _, resource := range info.Resources { + // if unique identifier is empty do not map the resource to analyzer result + if resource.ID == "" { + continue + } // if resource has permission it is binded resource if resource.Permission != "" { binding := analyzers.Binding{ @@ -141,6 +145,7 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult { Name: resource.Name, FullyQualifiedName: resource.ID, Type: resource.Type, + Metadata: map[string]any{}, // to avoid panic }, Permission: analyzers.Permission{ Value: resource.Permission, @@ -158,20 +163,13 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult { Name: resource.Name, FullyQualifiedName: resource.ID, Type: resource.Type, + Metadata: map[string]any{}, } for key, value := range resource.Metadata { unboundedResource.Metadata[key] = value } - if resource.Parent != nil { - unboundedResource.Parent = &analyzers.Resource{ - Name: resource.Parent.Name, - FullyQualifiedName: resource.Parent.ID, - Type: resource.Parent.Type, - } - } - result.UnboundedResources = append(result.UnboundedResources, unboundedResource) } } diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go new file mode 100644 index 000000000000..772542177848 --- /dev/null +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go @@ -0,0 +1,102 @@ +package elevenlabs + +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 result_output.json +var expectedOutput []byte + +func TestAnalyzer_Analyze(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) + defer cancel() + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + + key := testSecrets.MustGetField("ELEVENLABS") + + tests := []struct { + name string + key string + want []byte // JSON string + wantErr bool + }{ + { + name: "valid ElevenLabs full access key", + key: key, + want: 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 + }) +} diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go index 1e118eb4aa90..30b32550630e 100644 --- a/pkg/analyzer/analyzers/elevenlabs/requests.go +++ b/pkg/analyzer/analyzers/elevenlabs/requests.go @@ -78,6 +78,7 @@ type HistoryResponse struct { } `json:"history"` } +// VoiceResponse is the /voices API response type VoicesResponse struct { Voices []struct { ID string `json:"voice_id"` @@ -86,6 +87,7 @@ type VoicesResponse struct { } `json:"voices"` } +// ProjectsResponse is the /projects API response type ProjectsResponse struct { Projects []struct { ID string `json:"project_id"` @@ -95,6 +97,7 @@ type ProjectsResponse struct { } `json:"projects"` } +// PronunciationDictionaries is the /pronunciation-dictionaries API response type PronunciationDictionariesResponse struct { PronunciationDictionaries []struct { ID string `json:"id"` @@ -102,11 +105,13 @@ type PronunciationDictionariesResponse struct { } `json:"pronunciation_dictionaries"` } +// Models is the /models API response type ModelsResponse struct { ID string `json:"model_id"` Name string `json:"name"` } +// AgentsResponse is the /agents API response type AgentsResponse struct { Agents []struct { ID string `json:"agent_id"` @@ -115,6 +120,7 @@ type AgentsResponse struct { } `json:"agents"` } +// ConversationResponse is the /conversation API response type ConversationResponse struct { Conversations []struct { AgentID string `json:"agent_id"` @@ -693,7 +699,7 @@ func getAgents(client *http.Client, key string, secretInfo *SecretInfo) error { } secretInfo.Resources = append(secretInfo.Resources, resource) // get agent conversations - if err := getConversation(client, key, agent.ID, &resource, secretInfo); err != nil { + if err := getConversation(client, key, agent.ID, secretInfo); err != nil { return err } } @@ -705,7 +711,7 @@ func getAgents(client *http.Client, key string, secretInfo *SecretInfo) error { } // getConversation list all agent conversations using the key and agentID passed and add them to secret info -func getConversation(client *http.Client, key, agentID string, parent *Resource, secretInfo *SecretInfo) error { +func getConversation(client *http.Client, key, agentID string, secretInfo *SecretInfo) error { apiUrl := fmt.Sprintf("https://api.elevenlabs.io/v1/convai/conversations?agent_id=%s", agentID) response, statusCode, err := makeElevenLabsRequest(client, apiUrl, http.MethodGet, key) if err != nil { @@ -730,7 +736,6 @@ func getConversation(client *http.Client, key, agentID string, parent *Resource, Metadata: map[string]string{ "status": conversation.Status, }, - Parent: parent, }) } diff --git a/pkg/analyzer/analyzers/elevenlabs/result_output.json b/pkg/analyzer/analyzers/elevenlabs/result_output.json new file mode 100644 index 000000000000..3935a5899817 --- /dev/null +++ b/pkg/analyzer/analyzers/elevenlabs/result_output.json @@ -0,0 +1,527 @@ +{ + "AnalyzerType": 4, + "Bindings": [ + { + "Resource": { + "Name": "Trufflesecurity", + "FullyQualifiedName": "WKPePc2TgwawiHdwLf3AlvYQ7uF3", + "Type": "User", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "user_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "", + "FullyQualifiedName": "6CyAEZuVefy2XTPpDUPw", + "Type": "History", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "speech_history_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "", + "FullyQualifiedName": "eQrOq95SxEE2Giqwge3t", + "Type": "History", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "speech_history_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "", + "FullyQualifiedName": "0GY75sDfmOo3d99VCbrr", + "Type": "History", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "speech_history_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "", + "FullyQualifiedName": "Q1gHSXi5FFkVdvuRe6L0", + "Type": "History", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "speech_history_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "", + "FullyQualifiedName": "rUREhM2RHdoGVlBAnba2", + "Type": "History", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "speech_history_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "", + "FullyQualifiedName": "6ET44QjuCvNa0RdTLxqT", + "Type": "History", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "speech_history_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Aria", + "FullyQualifiedName": "9BWtsMINqrJLrRacOk9x", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Roger", + "FullyQualifiedName": "CwhRBWXzGAHq8TQ4Fs17", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Sarah", + "FullyQualifiedName": "EXAVITQu4vr4xnSDxMaL", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Laura", + "FullyQualifiedName": "FGY2WhTYpPnrIDTdsKH5", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Charlie", + "FullyQualifiedName": "IKne3meq5aSn9XLyUdCD", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "George", + "FullyQualifiedName": "JBFqnCBsd6RMkjVDRZzb", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Callum", + "FullyQualifiedName": "N2lVS1w4EtoT3dr4eOWO", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "River", + "FullyQualifiedName": "SAz9YHcvj6GT2YYXdXww", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Liam", + "FullyQualifiedName": "TX3LPaxmHKxFdv7VOQHJ", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Charlotte", + "FullyQualifiedName": "XB0fDUnXU5powFXDhCwa", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Alice", + "FullyQualifiedName": "Xb7hH8MSUJpSbSDYk0k2", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Matilda", + "FullyQualifiedName": "XrExE9yKIg1WjnnlVkGX", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Will", + "FullyQualifiedName": "bIHbv24MWmeRgasZH58o", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Jessica", + "FullyQualifiedName": "cgSgspJ2msm6clMCkdW9", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Eric", + "FullyQualifiedName": "cjVigY5qzO86Huf0OWal", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Chris", + "FullyQualifiedName": "iP95p4xoKVk53GoZ742B", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Brian", + "FullyQualifiedName": "nPczCjzI2devNBz1zQrb", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Daniel", + "FullyQualifiedName": "onwK4e9ZLuTAKqWW03F9", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Lily", + "FullyQualifiedName": "pFZP5JQG7iQjIQuC4Bku", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Bill", + "FullyQualifiedName": "pqHfZKP75CvOlQylNhV4", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, + "Parent": null + }, + "Permission": { + "Value": "voices_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Eleven Multilingual v2", + "FullyQualifiedName": "eleven_multilingual_v2", + "Type": "Model", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "models_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Eleven Flash v2.5", + "FullyQualifiedName": "eleven_flash_v2_5", + "Type": "Model", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "models_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Eleven Turbo v2.5", + "FullyQualifiedName": "eleven_turbo_v2_5", + "Type": "Model", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "models_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Eleven Turbo v2", + "FullyQualifiedName": "eleven_turbo_v2", + "Type": "Model", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "models_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Eleven Flash v2", + "FullyQualifiedName": "eleven_flash_v2", + "Type": "Model", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "models_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Eleven Multilingual v2", + "FullyQualifiedName": "eleven_multilingual_sts_v2", + "Type": "Model", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "models_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Eleven English v2", + "FullyQualifiedName": "eleven_english_sts_v2", + "Type": "Model", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "models_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Eleven Multilingual v1", + "FullyQualifiedName": "eleven_multilingual_v1", + "Type": "Model", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "models_read", + "Parent": null + } + }, + { + "Resource": { + "Name": "Eleven English v1", + "FullyQualifiedName": "eleven_monolingual_v1", + "Type": "Model", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "models_read", + "Parent": null + } + } + ], + "UnboundedResources": [ + { + "Name": "Support agent", + "FullyQualifiedName": "sBXUBEfy1ghoGEWWAzIJ", + "Type": "Agent", + "Metadata": { + "access level": "admin" + }, + "Parent": null + } + ], + "Metadata": { + "Valid_Key": true + } +} \ No newline at end of file diff --git a/pkg/detectors/elevenlabs/v1/elevenlabs.go b/pkg/detectors/elevenlabs/v1/elevenlabs.go index 0beb2346fd01..2edda0cdce8d 100644 --- a/pkg/detectors/elevenlabs/v1/elevenlabs.go +++ b/pkg/detectors/elevenlabs/v1/elevenlabs.go @@ -74,6 +74,12 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result s1.ExtraData["Tier"] = userResponse.Subscription.Tier } s1.SetVerificationError(verificationErr, match) + + if s1.Verified { + s1.AnalysisInfo = map[string]string{ + "key": match, + } + } } results = append(results, s1) diff --git a/pkg/detectors/elevenlabs/v2/elevenlabs.go b/pkg/detectors/elevenlabs/v2/elevenlabs.go index ed74d06296bd..1e094d12e91a 100644 --- a/pkg/detectors/elevenlabs/v2/elevenlabs.go +++ b/pkg/detectors/elevenlabs/v2/elevenlabs.go @@ -71,6 +71,12 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result s1.ExtraData["Tier"] = userResponse.Subscription.Tier } s1.SetVerificationError(verificationErr, match) + + if s1.Verified { + s1.AnalysisInfo = map[string]string{ + "key": match, + } + } } results = append(results, s1) diff --git a/pkg/detectors/elevenlabs/v2/elevenlabs_integration_test.go b/pkg/detectors/elevenlabs/v2/elevenlabs_integration_test.go index 1d439f377cd7..759c58c439e4 100644 --- a/pkg/detectors/elevenlabs/v2/elevenlabs_integration_test.go +++ b/pkg/detectors/elevenlabs/v2/elevenlabs_integration_test.go @@ -54,7 +54,7 @@ func TestElevenlabs_FromChunk(t *testing.T) { Verified: true, ExtraData: map[string]string{ "version": "2", - "Name": "Ahmed", + "Name": "Trufflesecurity", "Tier": "free", }, }, @@ -150,7 +150,7 @@ func TestElevenlabs_FromChunk(t *testing.T) { t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError()) } } - ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError") + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "AnalysisInfo") if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { t.Errorf("Elevenlabs.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } From 1937f36ea45c6d0fb87034363dec31a9b3d16a74 Mon Sep 17 00:00:00 2001 From: kashif khan Date: Tue, 28 Jan 2025 14:45:42 +0500 Subject: [PATCH 16/25] added go generate command --- pkg/analyzer/analyzers/elevenlabs/elevenlabs.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index 41b5d5bb8d3d..d1195d3079e5 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -1,3 +1,4 @@ +//go:generate generate_permissions permissions.yaml permissions.go elevenlabs package elevenlabs import ( From 3da2a78e988b905e39cf450b52013c6987e4f5ca Mon Sep 17 00:00:00 2001 From: kashif khan Date: Wed, 29 Jan 2025 19:03:21 +0500 Subject: [PATCH 17/25] optimized the code and resolved the comments --- .../analyzers/elevenlabs/elevenlabs.go | 300 +++++++++++------- pkg/analyzer/analyzers/elevenlabs/requests.go | 16 +- 2 files changed, 189 insertions(+), 127 deletions(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index d1195d3079e5..9ef97016ee11 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -8,8 +8,11 @@ import ( "net/http" "os" "slices" + "strings" + "sync" "github.com/fatih/color" + "github.com/google/uuid" "github.com/jedib0t/go-pretty/table" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers" @@ -25,12 +28,12 @@ type Analyzer struct { // SecretInfo hold information about key type SecretInfo struct { - User User // the owner of key - Valid bool - Reference string - Permissions []string // list of Permissions assigned to the key - Resources []Resource // list of resources the key has access to - Misc map[string]string + User User // the owner of key + Valid bool + Reference string + Permissions []string // list of Permissions assigned to the key + ElevenLabsResources []ElevenLabsResource // list of resources the key has access to + Misc map[string]string } // User hold the information about user to whom the key belongs to @@ -41,14 +44,13 @@ type User struct { SubscriptionStatus string } -// Resources hold information about the resources the key has access -type Resource struct { +// ElevenLabsResource hold information about the elevenlabs resource the key has access +type ElevenLabsResource struct { ID string Name string Type string Metadata map[string]string Permission string - Parent *Resource } func (a Analyzer) Type() analyzers.AnalyzerType { @@ -77,19 +79,22 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) { var secretInfo = &SecretInfo{} - // validate the key and get user information - if err := validateKey(client, key, secretInfo); err != nil { + // fetch user information using the key + user, err := fetchUser(client, key) + if err != nil { return nil, err } - if secretInfo.Valid { - // Get resources - if err := getResources(client, key, secretInfo); err != nil { - return nil, err - } + secretInfo.Valid = true + + // if user is not nil, that means the key has user read permission. Set the user information in secret info user + // user can only be nil when the key is valid but it does not have a user read permission + if user != nil { + elevenLabsUserToSecretInfoUser(*user, secretInfo) } - if err := getUnboundedResources(client, key, secretInfo); err != nil { + // get elevenlabs resources with permissions + if err := getElevenLabsResources(client, key, secretInfo); err != nil { return nil, err } @@ -99,8 +104,8 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) { func AnalyzeAndPrintPermissions(cfg *config.Config, key string) { info, err := AnalyzePermissions(cfg, key) if err != nil { + // just print the error in cli and continue as a partial success color.Red("[x] Error : %s", err.Error()) - return } if info == nil { @@ -115,7 +120,7 @@ func AnalyzeAndPrintPermissions(cfg *config.Config, key string) { // print permissions printPermissions(info.Permissions) // print resources - printResources(info.Resources) + printElevenLabsResources(info.ElevenLabsResources) color.Yellow("\n[i] Expires: Never") } @@ -133,18 +138,20 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult { Bindings: make([]analyzers.Binding, 0), } + // for resources to be uniquely identified, we need a unique id to be appended in resource fully qualified name + uniqueId := info.User.ID + if uniqueId == "" { + uniqueId = uuid.NewString() + } + // extract information from resource to create bindings and append to result bindings - for _, resource := range info.Resources { - // if unique identifier is empty do not map the resource to analyzer result - if resource.ID == "" { - continue - } + for _, resource := range info.ElevenLabsResources { // if resource has permission it is binded resource if resource.Permission != "" { binding := analyzers.Binding{ Resource: analyzers.Resource{ Name: resource.Name, - FullyQualifiedName: resource.ID, + FullyQualifiedName: fmt.Sprintf("%s/%s/%s", uniqueId, resource.Type, resource.ID), // e.g: /Model/eleven_flash_v2_5 Type: resource.Type, Metadata: map[string]any{}, // to avoid panic }, @@ -162,7 +169,7 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult { // if resource is missing permission it is an unbounded resource unboundedResource := analyzers.Resource{ Name: resource.Name, - FullyQualifiedName: resource.ID, + FullyQualifiedName: fmt.Sprintf("%s/%s/%s", uniqueId, resource.Type, resource.ID), Type: resource.Type, Metadata: map[string]any{}, } @@ -180,11 +187,11 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult { return &result } -// validateKey check if the key is valid and get the user information if it's valid -func validateKey(client *http.Client, key string, secretInfo *SecretInfo) error { +// fetchUser fetch elevenlabs user information associated with the key +func fetchUser(client *http.Client, key string) (*User, error) { response, statusCode, err := makeElevenLabsRequest(client, permissionToAPIMap[UserRead], http.MethodGet, key) if err != nil { - return err + return nil, err } switch statusCode { @@ -192,146 +199,201 @@ func validateKey(client *http.Client, key string, secretInfo *SecretInfo) error var user UserResponse if err := json.Unmarshal(response, &user); err != nil { - return err + return nil, err } - // map info to secretInfo - secretInfo.Valid = true - secretInfo.User = User{ + return &User{ ID: user.UserID, Name: user.FirstName, SubscriptionTier: user.Subscription.Tier, SubscriptionStatus: user.Subscription.Status, - } - // add user read scope to secret info - secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[UserRead]) - // map resource to secret info - secretInfo.Resources = append(secretInfo.Resources, Resource{ - ID: user.UserID, - Name: user.FirstName, - Type: "User", - Permission: PermissionStrings[UserRead], - }) - - return nil + }, nil case http.StatusUnauthorized: var errorResp ErrorResponse if err := json.Unmarshal(response, &errorResp); err != nil { - return err + return nil, err } if errorResp.Detail.Status == InvalidAPIKey || errorResp.Detail.Status == NotVerifiable { - return errors.New("invalid api key") + return nil, errors.New("invalid api key") } else if errorResp.Detail.Status == MissingPermissions { // key is missing user read permissions but is valid - secretInfo.Valid = true - color.Yellow("\n[!] API Key missing user read permissions") + return nil, nil } - return nil + return nil, nil default: - return fmt.Errorf("unexpected status code: %d", statusCode) + return nil, fmt.Errorf("unexpected status code: %d", statusCode) } } +// elevenLabsUserToSecretInfoUser set the elevenlabs user information to secretInfo user +func elevenLabsUserToSecretInfoUser(user User, secretInfo *SecretInfo) { + secretInfo.User = user + // add user read scope to secret info + secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[UserRead]) + // map resource to secret info + // as user is accessable through a specific permission and has a unique id it is also a resource + secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{ + ID: user.ID, + Name: user.Name, + Type: "User", + Permission: PermissionStrings[UserRead], + }) +} + /* -getResources gather resources the key can access +getElevenLabsResources gather resources the key can access Note: The permissions in eleven labs is either Read or Read and Write. There is not separate permission for Write. -If a particular write permission exist that means the read also exist. -So for API calls that does not return any resource data, we make the write permissions API calls first -and if they were as expected we skip the read API calls and add read permission directly. -If write permission API calls was not as expected than only we make read permission API calls -This we only do for those API calls which does not add any resources to secretInfo */ -func getResources(client *http.Client, key string, secretInfo *SecretInfo) error { +func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretInfo) error { + var ( + aggregatedErrs = make([]string, 0) + errChan = make(chan error, 17) // buffer for 17 errors - one per API call + wg sync.WaitGroup + ) + // history - if err := getHistory(client, key, secretInfo); err != nil { - return err - } + wg.Add(1) + go func() { + defer wg.Done() + if err := getHistory(client, key, secretInfo); err != nil { + errChan <- err + } - if err := deleteHistory(client, key, secretInfo); err != nil { - return err - } + if err := deleteHistory(client, key, secretInfo); err != nil { + errChan <- err + } + }() // dubbings - if err := deleteDubbing(client, key, secretInfo); err != nil { - return err - } + wg.Add(1) + go func() { + defer wg.Done() + if err := deleteDubbing(client, key, secretInfo); err != nil { + errChan <- err + } - // if dubbing write permission was not added - if !permissionExist(secretInfo.Permissions, DubbingWrite) { - if err := getDebugging(client, key, secretInfo); err != nil { - return err + // if dubbing write permission was not added + if !permissionExist(secretInfo.Permissions, DubbingWrite) { + if err := getDebugging(client, key, secretInfo); err != nil { + errChan <- err + } } - } + }() // voices - if err := getVoices(client, key, secretInfo); err != nil { - return err - } + wg.Add(1) + go func() { + defer wg.Done() + if err := getVoices(client, key, secretInfo); err != nil { + errChan <- err + } - if err := deleteVoice(client, key, secretInfo); err != nil { - return err - } + if err := deleteVoice(client, key, secretInfo); err != nil { + errChan <- err + } + }() // projects - if err := getProjects(client, key, secretInfo); err != nil { - return err - } + wg.Add(1) + go func() { + defer wg.Done() + if err := getProjects(client, key, secretInfo); err != nil { + errChan <- err + } - if err := deleteProject(client, key, secretInfo); err != nil { - return err - } + if err := deleteProject(client, key, secretInfo); err != nil { + errChan <- err + } + }() // pronunciation dictionaries - if err := getPronunciationDictionaries(client, key, secretInfo); err != nil { - return err - } + wg.Add(1) + go func() { + defer wg.Done() + if err := getPronunciationDictionaries(client, key, secretInfo); err != nil { + errChan <- err + } - if err := removePronunciationDictionariesRule(client, key, secretInfo); err != nil { - return err - } + if err := removePronunciationDictionariesRule(client, key, secretInfo); err != nil { + errChan <- err + } + }() // models - if err := getModels(client, key, secretInfo); err != nil { - return err - } + wg.Add(1) + go func() { + defer wg.Done() + if err := getModels(client, key, secretInfo); err != nil { + errChan <- err + } + }() // audio native - if err := updateAudioNativeProject(client, key, secretInfo); err != nil { - return err - } + wg.Add(1) + go func() { + defer wg.Done() + if err := updateAudioNativeProject(client, key, secretInfo); err != nil { + errChan <- err + } + }() // workspace - if err := deleteInviteFromWorkspace(client, key, secretInfo); err != nil { - return err - } - - // text to speech - if err := textToSpeech(client, key, secretInfo); err != nil { - return err - } + wg.Add(1) + go func() { + defer wg.Done() + if err := deleteInviteFromWorkspace(client, key, secretInfo); err != nil { + errChan <- err + } + }() + + // speech + wg.Add(1) + go func() { + defer wg.Done() + if err := textToSpeech(client, key, secretInfo); err != nil { + errChan <- err + } - // voice changer - if err := speechToSpeech(client, key, secretInfo); err != nil { - return err - } + // voice changer + if err := speechToSpeech(client, key, secretInfo); err != nil { + errChan <- err + } + }() // audio isolation - if err := audioIsolation(client, key, secretInfo); err != nil { - return err - } + wg.Add(1) + go func() { + defer wg.Done() + if err := audioIsolation(client, key, secretInfo); err != nil { + errChan <- err + } + }() + + // agent + wg.Add(1) + go func() { + defer wg.Done() + // each agent can have a conversations which we get inside this function + if err := getAgents(client, key, secretInfo); err != nil { + errChan <- err + } + }() - return nil -} + // wait for all API calls to finish + wg.Wait() + close(errChan) + + // collect all errors + for err := range errChan { + aggregatedErrs = append(aggregatedErrs, err.Error()) + } -// getUnboundedResources gather resources which can be accessed without any permission -func getUnboundedResources(client *http.Client, key string, secretInfo *SecretInfo) error { - // each agent can have a conversations which we get inside this function - if err := getAgents(client, key, secretInfo); err != nil { - return err + if len(aggregatedErrs) > 0 { + return errors.New(strings.Join(aggregatedErrs, ", ")) } return nil @@ -365,7 +427,7 @@ func printPermissions(permissions []string) { t.Render() } -func printResources(resources []Resource) { +func printElevenLabsResources(resources []ElevenLabsResource) { color.Green("\n[i] Resources:") t := table.NewWriter() t.SetOutputMirror(os.Stdout) diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go index 30b32550630e..0ea4a2a59198 100644 --- a/pkg/analyzer/analyzers/elevenlabs/requests.go +++ b/pkg/analyzer/analyzers/elevenlabs/requests.go @@ -226,7 +226,7 @@ func getHistory(client *http.Client, key string, secretInfo *SecretInfo) error { secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[SpeechHistoryRead]) // map resource to secret info for _, historyItem := range history.History { - secretInfo.Resources = append(secretInfo.Resources, Resource{ + secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{ ID: historyItem.ID, Name: "", // no name Type: "History", @@ -323,7 +323,7 @@ func getVoices(client *http.Client, key string, secretInfo *SecretInfo) error { secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[VoicesRead]) // map resource to secret info for _, voice := range voices.Voices { - secretInfo.Resources = append(secretInfo.Resources, Resource{ + secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{ ID: voice.ID, Name: voice.Name, Type: "Voice", @@ -379,7 +379,7 @@ func getProjects(client *http.Client, key string, secretInfo *SecretInfo) error secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[ProjectsRead]) // map resource to secret info for _, project := range projects.Projects { - secretInfo.Resources = append(secretInfo.Resources, Resource{ + secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{ ID: project.ID, Name: project.Name, Type: "Project", @@ -442,7 +442,7 @@ func getPronunciationDictionaries(client *http.Client, key string, secretInfo *S secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[PronunciationDictionariesRead]) // map resource to secret info for _, pd := range PDs.PronunciationDictionaries { - secretInfo.Resources = append(secretInfo.Resources, Resource{ + secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{ ID: pd.ID, Name: pd.Name, Type: "Pronunciation Dictionary", @@ -502,7 +502,7 @@ func getModels(client *http.Client, key string, secretInfo *SecretInfo) error { secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[ModelsRead]) // map resource to secret info for _, model := range models { - secretInfo.Resources = append(secretInfo.Resources, Resource{ + secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{ ID: model.ID, Name: model.Name, Type: "Model", @@ -688,7 +688,7 @@ func getAgents(client *http.Client, key string, secretInfo *SecretInfo) error { // map resource to secret info for _, agent := range agents.Agents { - resource := Resource{ + resource := ElevenLabsResource{ ID: agent.ID, Name: agent.Name, Type: "Agent", @@ -697,7 +697,7 @@ func getAgents(client *http.Client, key string, secretInfo *SecretInfo) error { "access level": agent.AccessLevel, }, } - secretInfo.Resources = append(secretInfo.Resources, resource) + secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, resource) // get agent conversations if err := getConversation(client, key, agent.ID, secretInfo); err != nil { return err @@ -728,7 +728,7 @@ func getConversation(client *http.Client, key, agentID string, secretInfo *Secre // map resource to secret info for _, conversation := range conversations.Conversations { - secretInfo.Resources = append(secretInfo.Resources, Resource{ + secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{ ID: conversation.ID, Name: "", // no name Type: "Conversation", From 3fa44027df146cb74de3df70af067ecbccd27d1f Mon Sep 17 00:00:00 2001 From: kashif khan Date: Wed, 29 Jan 2025 19:53:59 +0500 Subject: [PATCH 18/25] fixed linter --- pkg/analyzer/analyzers/elevenlabs/elevenlabs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index 9ef97016ee11..b2cfabd55bd5 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -234,7 +234,7 @@ func elevenLabsUserToSecretInfoUser(user User, secretInfo *SecretInfo) { // add user read scope to secret info secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[UserRead]) // map resource to secret info - // as user is accessable through a specific permission and has a unique id it is also a resource + // as user is accessible through a specific permission and has a unique id it is also a resource secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{ ID: user.ID, Name: user.Name, From fe995861950aa2df0adfd66da7ca607b23dc348e Mon Sep 17 00:00:00 2001 From: Kashif Khan <70996046+kashifkhan0771@users.noreply.github.com> Date: Fri, 14 Feb 2025 11:04:18 +0500 Subject: [PATCH 19/25] fixed indentation --- pkg/analyzer/analyzers/analyzers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/analyzer/analyzers/analyzers.go b/pkg/analyzer/analyzers/analyzers.go index aabf8805d163..0cfcc6833a61 100644 --- a/pkg/analyzer/analyzers/analyzers.go +++ b/pkg/analyzer/analyzers/analyzers.go @@ -65,7 +65,7 @@ const ( AnalyzerTypeAsana AnalyzerTypeBitbucket AnalyzerTypeDockerHub - AnalyzerTypeElevenLabs + AnalyzerTypeElevenLabs AnalyzerTypeGitHub AnalyzerTypeGitLab AnalyzerTypeHuggingFace @@ -96,7 +96,7 @@ var analyzerTypeStrings = map[AnalyzerType]string{ AnalyzerTypeAsana: "Asana", AnalyzerTypeBitbucket: "Bitbucket", AnalyzerTypeDockerHub: "DockerHub", - AnalyzerTypeElevenLabs: "ElevenLabs", + AnalyzerTypeElevenLabs: "ElevenLabs", AnalyzerTypeGitHub: "GitHub", AnalyzerTypeGitLab: "GitLab", AnalyzerTypeHuggingFace: "HuggingFace", From a1bbc7d2e6b3817c8d728ac1299f06b1f605ce1d Mon Sep 17 00:00:00 2001 From: kashif khan Date: Thu, 20 Feb 2025 12:42:28 +0500 Subject: [PATCH 20/25] resolved comments --- .../analyzers/elevenlabs/elevenlabs.go | 46 ++- .../analyzers/elevenlabs/elevenlabs_test.go | 3 + .../analyzers/elevenlabs/result_output.json | 352 +++++++----------- pkg/analyzer/cli.go | 6 +- 4 files changed, 183 insertions(+), 224 deletions(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index b2cfabd55bd5..3d042709db3f 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -1,4 +1,3 @@ -//go:generate generate_permissions permissions.yaml permissions.go elevenlabs package elevenlabs import ( @@ -253,12 +252,17 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI aggregatedErrs = make([]string, 0) errChan = make(chan error, 17) // buffer for 17 errors - one per API call wg sync.WaitGroup + mu sync.RWMutex ) // history wg.Add(1) go func() { defer wg.Done() + + mu.Lock() + defer mu.Unlock() + if err := getHistory(client, key, secretInfo); err != nil { errChan <- err } @@ -272,6 +276,10 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI wg.Add(1) go func() { defer wg.Done() + + mu.Lock() + defer mu.Unlock() + if err := deleteDubbing(client, key, secretInfo); err != nil { errChan <- err } @@ -288,6 +296,10 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI wg.Add(1) go func() { defer wg.Done() + + mu.Lock() + defer mu.Unlock() + if err := getVoices(client, key, secretInfo); err != nil { errChan <- err } @@ -301,6 +313,10 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI wg.Add(1) go func() { defer wg.Done() + + mu.Lock() + defer mu.Unlock() + if err := getProjects(client, key, secretInfo); err != nil { errChan <- err } @@ -314,6 +330,10 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI wg.Add(1) go func() { defer wg.Done() + + mu.Lock() + defer mu.Unlock() + if err := getPronunciationDictionaries(client, key, secretInfo); err != nil { errChan <- err } @@ -327,6 +347,10 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI wg.Add(1) go func() { defer wg.Done() + + mu.Lock() + defer mu.Unlock() + if err := getModels(client, key, secretInfo); err != nil { errChan <- err } @@ -336,6 +360,10 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI wg.Add(1) go func() { defer wg.Done() + + mu.Lock() + defer mu.Unlock() + if err := updateAudioNativeProject(client, key, secretInfo); err != nil { errChan <- err } @@ -345,6 +373,10 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI wg.Add(1) go func() { defer wg.Done() + + mu.Lock() + defer mu.Unlock() + if err := deleteInviteFromWorkspace(client, key, secretInfo); err != nil { errChan <- err } @@ -354,6 +386,10 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI wg.Add(1) go func() { defer wg.Done() + + mu.Lock() + defer mu.Unlock() + if err := textToSpeech(client, key, secretInfo); err != nil { errChan <- err } @@ -368,6 +404,10 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI wg.Add(1) go func() { defer wg.Done() + + mu.Lock() + defer mu.Unlock() + if err := audioIsolation(client, key, secretInfo); err != nil { errChan <- err } @@ -377,6 +417,10 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI wg.Add(1) go func() { defer wg.Done() + + mu.Lock() + defer mu.Unlock() + // each agent can have a conversations which we get inside this function if err := getAgents(client, key, secretInfo); err != nil { errChan <- err diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go index 772542177848..57a23ac060a8 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go @@ -3,6 +3,7 @@ package elevenlabs import ( _ "embed" "encoding/json" + "fmt" "sort" "testing" "time" @@ -58,6 +59,8 @@ func TestAnalyzer_Analyze(t *testing.T) { t.Fatalf("could not marshal got to JSON: %s", err) } + fmt.Println(string(gotJSON)) + // Parse the expected JSON string var wantObj analyzers.AnalyzerResult if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil { diff --git a/pkg/analyzer/analyzers/elevenlabs/result_output.json b/pkg/analyzer/analyzers/elevenlabs/result_output.json index 3935a5899817..aeef5b7633bf 100644 --- a/pkg/analyzer/analyzers/elevenlabs/result_output.json +++ b/pkg/analyzer/analyzers/elevenlabs/result_output.json @@ -1,10 +1,10 @@ { - "AnalyzerType": 4, + "AnalyzerType": 6, "Bindings": [ { "Resource": { - "Name": "Trufflesecurity", - "FullyQualifiedName": "WKPePc2TgwawiHdwLf3AlvYQ7uF3", + "Name": "Ahmed", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/User/b9Rou9mHDmTYd8cdWkg2Yk4P2lq1", "Type": "User", "Metadata": {}, "Parent": null @@ -16,86 +16,23 @@ }, { "Resource": { - "Name": "", - "FullyQualifiedName": "6CyAEZuVefy2XTPpDUPw", - "Type": "History", - "Metadata": {}, - "Parent": null - }, - "Permission": { - "Value": "speech_history_read", - "Parent": null - } - }, - { - "Resource": { - "Name": "", - "FullyQualifiedName": "eQrOq95SxEE2Giqwge3t", - "Type": "History", - "Metadata": {}, - "Parent": null - }, - "Permission": { - "Value": "speech_history_read", - "Parent": null - } - }, - { - "Resource": { - "Name": "", - "FullyQualifiedName": "0GY75sDfmOo3d99VCbrr", - "Type": "History", - "Metadata": {}, - "Parent": null - }, - "Permission": { - "Value": "speech_history_read", - "Parent": null - } - }, - { - "Resource": { - "Name": "", - "FullyQualifiedName": "Q1gHSXi5FFkVdvuRe6L0", - "Type": "History", - "Metadata": {}, - "Parent": null - }, - "Permission": { - "Value": "speech_history_read", - "Parent": null - } - }, - { - "Resource": { - "Name": "", - "FullyQualifiedName": "rUREhM2RHdoGVlBAnba2", - "Type": "History", - "Metadata": {}, - "Parent": null - }, - "Permission": { - "Value": "speech_history_read", - "Parent": null - } - }, - { - "Resource": { - "Name": "", - "FullyQualifiedName": "6ET44QjuCvNa0RdTLxqT", - "Type": "History", - "Metadata": {}, + "Name": "Alice", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/Xb7hH8MSUJpSbSDYk0k2", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, "Parent": null }, "Permission": { - "Value": "speech_history_read", + "Value": "voices_read", "Parent": null } }, { "Resource": { "Name": "Aria", - "FullyQualifiedName": "9BWtsMINqrJLrRacOk9x", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/9BWtsMINqrJLrRacOk9x", "Type": "Voice", "Metadata": { "category": "premade" @@ -109,8 +46,8 @@ }, { "Resource": { - "Name": "Roger", - "FullyQualifiedName": "CwhRBWXzGAHq8TQ4Fs17", + "Name": "Bill", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/pqHfZKP75CvOlQylNhV4", "Type": "Voice", "Metadata": { "category": "premade" @@ -124,8 +61,8 @@ }, { "Resource": { - "Name": "Sarah", - "FullyQualifiedName": "EXAVITQu4vr4xnSDxMaL", + "Name": "Brian", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/nPczCjzI2devNBz1zQrb", "Type": "Voice", "Metadata": { "category": "premade" @@ -139,8 +76,8 @@ }, { "Resource": { - "Name": "Laura", - "FullyQualifiedName": "FGY2WhTYpPnrIDTdsKH5", + "Name": "Callum", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/N2lVS1w4EtoT3dr4eOWO", "Type": "Voice", "Metadata": { "category": "premade" @@ -155,7 +92,7 @@ { "Resource": { "Name": "Charlie", - "FullyQualifiedName": "IKne3meq5aSn9XLyUdCD", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/IKne3meq5aSn9XLyUdCD", "Type": "Voice", "Metadata": { "category": "premade" @@ -169,8 +106,8 @@ }, { "Resource": { - "Name": "George", - "FullyQualifiedName": "JBFqnCBsd6RMkjVDRZzb", + "Name": "Charlotte", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/XB0fDUnXU5powFXDhCwa", "Type": "Voice", "Metadata": { "category": "premade" @@ -184,8 +121,8 @@ }, { "Resource": { - "Name": "Callum", - "FullyQualifiedName": "N2lVS1w4EtoT3dr4eOWO", + "Name": "Chris", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/iP95p4xoKVk53GoZ742B", "Type": "Voice", "Metadata": { "category": "premade" @@ -199,8 +136,8 @@ }, { "Resource": { - "Name": "River", - "FullyQualifiedName": "SAz9YHcvj6GT2YYXdXww", + "Name": "Daniel", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/onwK4e9ZLuTAKqWW03F9", "Type": "Voice", "Metadata": { "category": "premade" @@ -214,143 +151,125 @@ }, { "Resource": { - "Name": "Liam", - "FullyQualifiedName": "TX3LPaxmHKxFdv7VOQHJ", - "Type": "Voice", - "Metadata": { - "category": "premade" - }, + "Name": "Eleven English v1", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_monolingual_v1", + "Type": "Model", + "Metadata": {}, "Parent": null }, "Permission": { - "Value": "voices_read", + "Value": "models_read", "Parent": null } }, { "Resource": { - "Name": "Charlotte", - "FullyQualifiedName": "XB0fDUnXU5powFXDhCwa", - "Type": "Voice", - "Metadata": { - "category": "premade" - }, + "Name": "Eleven English v2", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_english_sts_v2", + "Type": "Model", + "Metadata": {}, "Parent": null }, "Permission": { - "Value": "voices_read", + "Value": "models_read", "Parent": null } }, { "Resource": { - "Name": "Alice", - "FullyQualifiedName": "Xb7hH8MSUJpSbSDYk0k2", - "Type": "Voice", - "Metadata": { - "category": "premade" - }, + "Name": "Eleven Flash v2", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_flash_v2", + "Type": "Model", + "Metadata": {}, "Parent": null }, "Permission": { - "Value": "voices_read", + "Value": "models_read", "Parent": null } }, { "Resource": { - "Name": "Matilda", - "FullyQualifiedName": "XrExE9yKIg1WjnnlVkGX", - "Type": "Voice", - "Metadata": { - "category": "premade" - }, + "Name": "Eleven Flash v2.5", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_flash_v2_5", + "Type": "Model", + "Metadata": {}, "Parent": null }, "Permission": { - "Value": "voices_read", + "Value": "models_read", "Parent": null } }, { "Resource": { - "Name": "Will", - "FullyQualifiedName": "bIHbv24MWmeRgasZH58o", - "Type": "Voice", - "Metadata": { - "category": "premade" - }, + "Name": "Eleven Multilingual v1", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_v1", + "Type": "Model", + "Metadata": {}, "Parent": null }, "Permission": { - "Value": "voices_read", + "Value": "models_read", "Parent": null } }, { "Resource": { - "Name": "Jessica", - "FullyQualifiedName": "cgSgspJ2msm6clMCkdW9", - "Type": "Voice", - "Metadata": { - "category": "premade" - }, + "Name": "Eleven Multilingual v2", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_v2", + "Type": "Model", + "Metadata": {}, "Parent": null }, "Permission": { - "Value": "voices_read", + "Value": "models_read", "Parent": null } }, { "Resource": { - "Name": "Eric", - "FullyQualifiedName": "cjVigY5qzO86Huf0OWal", - "Type": "Voice", - "Metadata": { - "category": "premade" - }, + "Name": "Eleven Multilingual v2", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_sts_v2", + "Type": "Model", + "Metadata": {}, "Parent": null }, "Permission": { - "Value": "voices_read", + "Value": "models_read", "Parent": null } }, { "Resource": { - "Name": "Chris", - "FullyQualifiedName": "iP95p4xoKVk53GoZ742B", - "Type": "Voice", - "Metadata": { - "category": "premade" - }, + "Name": "Eleven Turbo v2", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_turbo_v2", + "Type": "Model", + "Metadata": {}, "Parent": null }, "Permission": { - "Value": "voices_read", + "Value": "models_read", "Parent": null } }, { "Resource": { - "Name": "Brian", - "FullyQualifiedName": "nPczCjzI2devNBz1zQrb", - "Type": "Voice", - "Metadata": { - "category": "premade" - }, + "Name": "Eleven Turbo v2.5", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_turbo_v2_5", + "Type": "Model", + "Metadata": {}, "Parent": null }, "Permission": { - "Value": "voices_read", + "Value": "models_read", "Parent": null } }, { "Resource": { - "Name": "Daniel", - "FullyQualifiedName": "onwK4e9ZLuTAKqWW03F9", + "Name": "Eric", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/cjVigY5qzO86Huf0OWal", "Type": "Voice", "Metadata": { "category": "premade" @@ -364,8 +283,8 @@ }, { "Resource": { - "Name": "Lily", - "FullyQualifiedName": "pFZP5JQG7iQjIQuC4Bku", + "Name": "George", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/JBFqnCBsd6RMkjVDRZzb", "Type": "Voice", "Metadata": { "category": "premade" @@ -379,8 +298,8 @@ }, { "Resource": { - "Name": "Bill", - "FullyQualifiedName": "pqHfZKP75CvOlQylNhV4", + "Name": "Jessica", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/cgSgspJ2msm6clMCkdW9", "Type": "Voice", "Metadata": { "category": "premade" @@ -394,133 +313,126 @@ }, { "Resource": { - "Name": "Eleven Multilingual v2", - "FullyQualifiedName": "eleven_multilingual_v2", - "Type": "Model", - "Metadata": {}, - "Parent": null - }, - "Permission": { - "Value": "models_read", - "Parent": null - } - }, - { - "Resource": { - "Name": "Eleven Flash v2.5", - "FullyQualifiedName": "eleven_flash_v2_5", - "Type": "Model", - "Metadata": {}, + "Name": "Laura", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/FGY2WhTYpPnrIDTdsKH5", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, "Parent": null }, "Permission": { - "Value": "models_read", + "Value": "voices_read", "Parent": null } }, { "Resource": { - "Name": "Eleven Turbo v2.5", - "FullyQualifiedName": "eleven_turbo_v2_5", - "Type": "Model", - "Metadata": {}, + "Name": "Liam", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/TX3LPaxmHKxFdv7VOQHJ", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, "Parent": null }, "Permission": { - "Value": "models_read", + "Value": "voices_read", "Parent": null } }, { "Resource": { - "Name": "Eleven Turbo v2", - "FullyQualifiedName": "eleven_turbo_v2", - "Type": "Model", - "Metadata": {}, + "Name": "Lily", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/pFZP5JQG7iQjIQuC4Bku", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, "Parent": null }, "Permission": { - "Value": "models_read", + "Value": "voices_read", "Parent": null } }, { "Resource": { - "Name": "Eleven Flash v2", - "FullyQualifiedName": "eleven_flash_v2", - "Type": "Model", - "Metadata": {}, + "Name": "Matilda", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/XrExE9yKIg1WjnnlVkGX", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, "Parent": null }, "Permission": { - "Value": "models_read", + "Value": "voices_read", "Parent": null } }, { "Resource": { - "Name": "Eleven Multilingual v2", - "FullyQualifiedName": "eleven_multilingual_sts_v2", - "Type": "Model", - "Metadata": {}, + "Name": "River", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/SAz9YHcvj6GT2YYXdXww", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, "Parent": null }, "Permission": { - "Value": "models_read", + "Value": "voices_read", "Parent": null } }, { "Resource": { - "Name": "Eleven English v2", - "FullyQualifiedName": "eleven_english_sts_v2", - "Type": "Model", - "Metadata": {}, + "Name": "Roger", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/CwhRBWXzGAHq8TQ4Fs17", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, "Parent": null }, "Permission": { - "Value": "models_read", + "Value": "voices_read", "Parent": null } }, { "Resource": { - "Name": "Eleven Multilingual v1", - "FullyQualifiedName": "eleven_multilingual_v1", - "Type": "Model", - "Metadata": {}, + "Name": "Sarah", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/EXAVITQu4vr4xnSDxMaL", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, "Parent": null }, "Permission": { - "Value": "models_read", + "Value": "voices_read", "Parent": null } }, { "Resource": { - "Name": "Eleven English v1", - "FullyQualifiedName": "eleven_monolingual_v1", - "Type": "Model", - "Metadata": {}, + "Name": "Will", + "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/bIHbv24MWmeRgasZH58o", + "Type": "Voice", + "Metadata": { + "category": "premade" + }, "Parent": null }, "Permission": { - "Value": "models_read", + "Value": "voices_read", "Parent": null } } ], - "UnboundedResources": [ - { - "Name": "Support agent", - "FullyQualifiedName": "sBXUBEfy1ghoGEWWAzIJ", - "Type": "Agent", - "Metadata": { - "access level": "admin" - }, - "Parent": null - } - ], + "UnboundedResources": null, "Metadata": { "Valid_Key": true } diff --git a/pkg/analyzer/cli.go b/pkg/analyzer/cli.go index ff8832233364..133b04754820 100644 --- a/pkg/analyzer/cli.go +++ b/pkg/analyzer/cli.go @@ -10,7 +10,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/asana" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/bitbucket" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/dockerhub" - "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/elevenlabs" + "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/elevenlabs" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/gitlab" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/huggingface" @@ -93,9 +93,9 @@ func Run(keyType string, secretInfo SecretInfo) { notion.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"]) case "dockerhub": dockerhub.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["username"], secretInfo.Parts["pat"]) - case "anthropic": + case "anthropic": anthropic.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"]) - case "elevenlabs": + case "elevenlabs": elevenlabs.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"]) } } From a7e1099cb75a50ec4dec7fcb5a5f582a9f1211da Mon Sep 17 00:00:00 2001 From: kashif khan Date: Thu, 20 Feb 2025 12:45:52 +0500 Subject: [PATCH 21/25] kicked imposter print statement --- pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go index 57a23ac060a8..772542177848 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go @@ -3,7 +3,6 @@ package elevenlabs import ( _ "embed" "encoding/json" - "fmt" "sort" "testing" "time" @@ -59,8 +58,6 @@ func TestAnalyzer_Analyze(t *testing.T) { t.Fatalf("could not marshal got to JSON: %s", err) } - fmt.Println(string(gotJSON)) - // Parse the expected JSON string var wantObj analyzers.AnalyzerResult if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil { From 6a2071f92e9b96b253bc54826e0f0aab7b458e7c Mon Sep 17 00:00:00 2001 From: kashif khan Date: Thu, 20 Feb 2025 12:48:00 +0500 Subject: [PATCH 22/25] formatted cli.go --- pkg/analyzer/cli.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/analyzer/cli.go b/pkg/analyzer/cli.go index 1b011df466ec..49b0ba3c73d9 100644 --- a/pkg/analyzer/cli.go +++ b/pkg/analyzer/cli.go @@ -98,7 +98,7 @@ func Run(keyType string, secretInfo SecretInfo) { anthropic.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"]) case "airtable": airtable.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"]) - case "elevenlabs": + case "elevenlabs": elevenlabs.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"]) } } From 5f625fd36d99b577ed21caa8321616ee88dcff94 Mon Sep 17 00:00:00 2001 From: Kashif Khan <70996046+kashifkhan0771@users.noreply.github.com> Date: Thu, 20 Feb 2025 18:45:01 +0500 Subject: [PATCH 23/25] Update pkg/analyzer/analyzers/elevenlabs/elevenlabs.go Co-authored-by: Eng Zer Jun --- pkg/analyzer/analyzers/elevenlabs/elevenlabs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index 3d042709db3f..219a0f1c0707 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -12,7 +12,7 @@ import ( "github.com/fatih/color" "github.com/google/uuid" - "github.com/jedib0t/go-pretty/table" + "github.com/jedib0t/go-pretty/v6/table" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config" From 416589456ab6d72813f4ff74ebd4a40e8d649bdb Mon Sep 17 00:00:00 2001 From: kashif khan Date: Fri, 21 Feb 2025 12:31:32 +0500 Subject: [PATCH 24/25] resolved comments --- pkg/analyzer/analyzers/elevenlabs/elevenlabs.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index 219a0f1c0707..461547453cbc 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -32,7 +32,6 @@ type SecretInfo struct { Reference string Permissions []string // list of Permissions assigned to the key ElevenLabsResources []ElevenLabsResource // list of resources the key has access to - Misc map[string]string } // User hold the information about user to whom the key belongs to From 665596eff7857361ce6c3b729d80a92bd9dcb236 Mon Sep 17 00:00:00 2001 From: kashif khan Date: Mon, 24 Feb 2025 11:48:19 +0500 Subject: [PATCH 25/25] added secret info methods which safely append and read permission and resources --- .../analyzers/elevenlabs/elevenlabs.go | 70 ++++++++----------- pkg/analyzer/analyzers/elevenlabs/requests.go | 36 +++++----- 2 files changed, 48 insertions(+), 58 deletions(-) diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go index 461547453cbc..472498c965a0 100644 --- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go +++ b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go @@ -32,6 +32,33 @@ type SecretInfo struct { Reference string Permissions []string // list of Permissions assigned to the key ElevenLabsResources []ElevenLabsResource // list of resources the key has access to + mu sync.RWMutex +} + +// AppendPermissions safely append new permission to secret info permissions list. +func (s *SecretInfo) AppendPermission(perm string) { + s.mu.Lock() + defer s.mu.Unlock() + + s.Permissions = append(s.Permissions, perm) +} + +// HasPermission safely read secret info permission list to check if passed permission exist in the list. +func (s *SecretInfo) HasPermission(perm Permission) bool { + s.mu.Lock() + defer s.mu.Unlock() + + permissionString, _ := perm.ToString() + + return slices.Contains(s.Permissions, permissionString) +} + +// AppendResource safely append new resource to secret info elevenlabs resource list. +func (s *SecretInfo) AppendResource(resource ElevenLabsResource) { + s.mu.Lock() + defer s.mu.Unlock() + + s.ElevenLabsResources = append(s.ElevenLabsResources, resource) } // User hold the information about user to whom the key belongs to @@ -251,7 +278,6 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI aggregatedErrs = make([]string, 0) errChan = make(chan error, 17) // buffer for 17 errors - one per API call wg sync.WaitGroup - mu sync.RWMutex ) // history @@ -259,9 +285,6 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI go func() { defer wg.Done() - mu.Lock() - defer mu.Unlock() - if err := getHistory(client, key, secretInfo); err != nil { errChan <- err } @@ -276,15 +299,12 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI go func() { defer wg.Done() - mu.Lock() - defer mu.Unlock() - if err := deleteDubbing(client, key, secretInfo); err != nil { errChan <- err } // if dubbing write permission was not added - if !permissionExist(secretInfo.Permissions, DubbingWrite) { + if !secretInfo.HasPermission(DubbingWrite) { if err := getDebugging(client, key, secretInfo); err != nil { errChan <- err } @@ -296,9 +316,6 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI go func() { defer wg.Done() - mu.Lock() - defer mu.Unlock() - if err := getVoices(client, key, secretInfo); err != nil { errChan <- err } @@ -313,9 +330,6 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI go func() { defer wg.Done() - mu.Lock() - defer mu.Unlock() - if err := getProjects(client, key, secretInfo); err != nil { errChan <- err } @@ -330,9 +344,6 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI go func() { defer wg.Done() - mu.Lock() - defer mu.Unlock() - if err := getPronunciationDictionaries(client, key, secretInfo); err != nil { errChan <- err } @@ -347,9 +358,6 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI go func() { defer wg.Done() - mu.Lock() - defer mu.Unlock() - if err := getModels(client, key, secretInfo); err != nil { errChan <- err } @@ -360,9 +368,6 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI go func() { defer wg.Done() - mu.Lock() - defer mu.Unlock() - if err := updateAudioNativeProject(client, key, secretInfo); err != nil { errChan <- err } @@ -373,9 +378,6 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI go func() { defer wg.Done() - mu.Lock() - defer mu.Unlock() - if err := deleteInviteFromWorkspace(client, key, secretInfo); err != nil { errChan <- err } @@ -386,9 +388,6 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI go func() { defer wg.Done() - mu.Lock() - defer mu.Unlock() - if err := textToSpeech(client, key, secretInfo); err != nil { errChan <- err } @@ -404,9 +403,6 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI go func() { defer wg.Done() - mu.Lock() - defer mu.Unlock() - if err := audioIsolation(client, key, secretInfo); err != nil { errChan <- err } @@ -417,9 +413,6 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI go func() { defer wg.Done() - mu.Lock() - defer mu.Unlock() - // each agent can have a conversations which we get inside this function if err := getAgents(client, key, secretInfo); err != nil { errChan <- err @@ -442,13 +435,6 @@ func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretI return nil } -// permissionExist returns if particular permission exist in the list -func permissionExist(permissionsList []string, permission Permission) bool { - permissionString, _ := permission.ToString() - - return slices.Contains(permissionsList, permissionString) -} - // cli print functions func printUser(user User) { color.Green("\n[i] User:") diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go index 0ea4a2a59198..ea4d3ec54b33 100644 --- a/pkg/analyzer/analyzers/elevenlabs/requests.go +++ b/pkg/analyzer/analyzers/elevenlabs/requests.go @@ -3,6 +3,7 @@ package elevenlabs import ( "bytes" "encoding/json" + "errors" "fmt" "io" "mime/multipart" @@ -223,10 +224,10 @@ func getHistory(client *http.Client, key string, secretInfo *SecretInfo) error { } // add history read scope to secret info - secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[SpeechHistoryRead]) + secretInfo.AppendPermission(PermissionStrings[SpeechHistoryRead]) // map resource to secret info for _, historyItem := range history.History { - secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{ + secretInfo.AppendResource(ElevenLabsResource{ ID: historyItem.ID, Name: "", // no name Type: "History", @@ -276,7 +277,7 @@ func deleteDubbing(client *http.Client, key string, secretInfo *SecretInfo) erro } // add read scope of dubbing to avoid get dubbing api call - secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[DubbingRead]) + secretInfo.AppendPermission(PermissionStrings[DubbingRead]) return nil case http.StatusUnauthorized: @@ -320,10 +321,10 @@ func getVoices(client *http.Client, key string, secretInfo *SecretInfo) error { } // add voices read scope to secret info - secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[VoicesRead]) + secretInfo.AppendPermission(PermissionStrings[VoicesRead]) // map resource to secret info for _, voice := range voices.Voices { - secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{ + secretInfo.AppendResource(ElevenLabsResource{ ID: voice.ID, Name: voice.Name, Type: "Voice", @@ -376,10 +377,10 @@ func getProjects(client *http.Client, key string, secretInfo *SecretInfo) error } // add project read scope to secret info - secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[ProjectsRead]) + secretInfo.AppendPermission(PermissionStrings[ProjectsRead]) // map resource to secret info for _, project := range projects.Projects { - secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{ + secretInfo.AppendResource(ElevenLabsResource{ ID: project.ID, Name: project.Name, Type: "Project", @@ -439,10 +440,10 @@ func getPronunciationDictionaries(client *http.Client, key string, secretInfo *S } // add voices read scope to secret info - secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[PronunciationDictionariesRead]) + secretInfo.AppendPermission(PermissionStrings[PronunciationDictionariesRead]) // map resource to secret info for _, pd := range PDs.PronunciationDictionaries { - secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{ + secretInfo.AppendResource(ElevenLabsResource{ ID: pd.ID, Name: pd.Name, Type: "Pronunciation Dictionary", @@ -499,10 +500,10 @@ func getModels(client *http.Client, key string, secretInfo *SecretInfo) error { } // add models read scope to secret info - secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[ModelsRead]) + secretInfo.AppendPermission(PermissionStrings[ModelsRead]) // map resource to secret info for _, model := range models { - secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{ + secretInfo.AppendResource(ElevenLabsResource{ ID: model.ID, Name: model.Name, Type: "Model", @@ -544,7 +545,7 @@ func updateAudioNativeProject(client *http.Client, key string, secretInfo *Secre } // add read permission as no separate API exist to check read audio native permission - secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[AudioNativeRead]) + secretInfo.AppendPermission(PermissionStrings[AudioNativeRead]) return nil case http.StatusUnauthorized: return handleErrorStatus(response, "", secretInfo, MissingPermissions) @@ -575,7 +576,7 @@ func deleteInviteFromWorkspace(client *http.Client, key string, secretInfo *Secr } // add read permission as no separate API exist to check workspace read permission - secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[WorkspaceRead]) + secretInfo.AppendPermission(PermissionStrings[WorkspaceRead]) return nil case http.StatusUnauthorized: return handleErrorStatus(response, "", secretInfo, MissingPermissions) @@ -697,7 +698,7 @@ func getAgents(client *http.Client, key string, secretInfo *SecretInfo) error { "access level": agent.AccessLevel, }, } - secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, resource) + secretInfo.AppendResource(resource) // get agent conversations if err := getConversation(client, key, agent.ID, secretInfo); err != nil { return err @@ -728,7 +729,7 @@ func getConversation(client *http.Client, key, agentID string, secretInfo *Secre // map resource to secret info for _, conversation := range conversations.Conversations { - secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{ + secretInfo.AppendResource(ElevenLabsResource{ ID: conversation.ID, Name: "", // no name Type: "Conversation", @@ -756,7 +757,10 @@ func handleErrorStatus(response []byte, permissionToAdd string, secretInfo *Secr // if permission to add was passed and it was expected error status add the permission if permissionToAdd != "" && ok { - secretInfo.Permissions = append(secretInfo.Permissions, permissionToAdd) + secretInfo.AppendPermission(permissionToAdd) + } else if permissionToAdd != "" && !ok { + // if permission to add was passed and it was unexpected error status - return error + return errors.New("unexpected error response") } return nil