elevenlabs analyzer (#3850)
Lint / golangci-lint (push) Waiting to run
Lint / semgrep (push) Waiting to run
Release / Release (push) Waiting to run
Scan for secrets / test (push) Waiting to run
Test / test (push) Waiting to run
Test / test-community (push) Waiting to run
Lint / golangci-lint (push) Waiting to run
Lint / semgrep (push) Waiting to run
Release / Release (push) Waiting to run
Scan for secrets / test (push) Waiting to run
Test / test (push) Waiting to run
Test / test-community (push) Waiting to run
* added analyzer initial working structure * restructured the resources * added history read api * added history delete api * fixed http method issue * added delete dubbing api and refactored the code * added dubbing read api * optimized requests and added projects api * added pronunciation dictionaries apis * fixed linter * added models,audionative,workspace apis * added some last apis * Deleted \ * fixed linter * few more enhancements and test cases added * added go generate command * optimized the code and resolved the comments * fixed linter * fixed indentation * resolved comments * kicked imposter print statement * formatted cli.go * Update pkg/analyzer/analyzers/elevenlabs/elevenlabs.go Co-authored-by: Eng Zer Jun <[email protected]> * resolved comments * added secret info methods which safely append and read permission and resources --------- Co-authored-by: Eng Zer Jun <[email protected]>
This commit is contained in:
co-authored by
Eng Zer Jun
parent
03e8af1075
commit
f7ff9980e3
@@ -65,6 +65,7 @@ const (
|
||||
AnalyzerTypeAsana
|
||||
AnalyzerTypeBitbucket
|
||||
AnalyzerTypeDockerHub
|
||||
AnalyzerTypeElevenLabs
|
||||
AnalyzerTypeGitHub
|
||||
AnalyzerTypeGitLab
|
||||
AnalyzerTypeHuggingFace
|
||||
@@ -97,7 +98,9 @@ var analyzerTypeStrings = map[AnalyzerType]string{
|
||||
AnalyzerAnthropic: "Anthropic",
|
||||
AnalyzerTypeAsana: "Asana",
|
||||
AnalyzerTypeBitbucket: "Bitbucket",
|
||||
AnalyzerTypeDigitalOcean: "DigitalOcean",
|
||||
AnalyzerTypeDockerHub: "DockerHub",
|
||||
AnalyzerTypeElevenLabs: "ElevenLabs",
|
||||
AnalyzerTypeGitHub: "GitHub",
|
||||
AnalyzerTypeGitLab: "GitLab",
|
||||
AnalyzerTypeHuggingFace: "HuggingFace",
|
||||
@@ -117,7 +120,6 @@ var analyzerTypeStrings = map[AnalyzerType]string{
|
||||
AnalyzerTypeTwilio: "Twilio",
|
||||
AnalyzerTypePrivateKey: "PrivateKey",
|
||||
AnalyzerTypeNotion: "Notion",
|
||||
AnalyzerTypeDigitalOcean: "DigitalOcean",
|
||||
// Add new mappings here
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,468 @@
|
||||
package elevenlabs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jedib0t/go-pretty/v6/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
|
||||
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
|
||||
type User struct {
|
||||
ID string
|
||||
Name string
|
||||
SubscriptionTier string
|
||||
SubscriptionStatus string
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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{}
|
||||
|
||||
// fetch user information using the key
|
||||
user, err := fetchUser(client, key)
|
||||
if 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)
|
||||
}
|
||||
|
||||
// get elevenlabs resources with permissions
|
||||
if err := getElevenLabsResources(client, key, secretInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return secretInfo, nil
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
if info == nil {
|
||||
color.Red("[x] Error : %s", "No information found")
|
||||
return
|
||||
}
|
||||
|
||||
if info.Valid {
|
||||
color.Green("[!] Valid ElevenLabs API key\n\n")
|
||||
// print user information
|
||||
printUser(info.User)
|
||||
// print permissions
|
||||
printPermissions(info.Permissions)
|
||||
// print resources
|
||||
printElevenLabsResources(info.ElevenLabsResources)
|
||||
|
||||
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, 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.ElevenLabsResources {
|
||||
// if resource has permission it is binded resource
|
||||
if resource.Permission != "" {
|
||||
binding := analyzers.Binding{
|
||||
Resource: analyzers.Resource{
|
||||
Name: resource.Name,
|
||||
FullyQualifiedName: fmt.Sprintf("%s/%s/%s", uniqueId, resource.Type, resource.ID), // e.g: <user_id>/Model/eleven_flash_v2_5
|
||||
Type: resource.Type,
|
||||
Metadata: map[string]any{}, // to avoid panic
|
||||
},
|
||||
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: fmt.Sprintf("%s/%s/%s", uniqueId, resource.Type, resource.ID),
|
||||
Type: resource.Type,
|
||||
Metadata: map[string]any{},
|
||||
}
|
||||
|
||||
for key, value := range resource.Metadata {
|
||||
unboundedResource.Metadata[key] = value
|
||||
}
|
||||
|
||||
result.UnboundedResources = append(result.UnboundedResources, unboundedResource)
|
||||
}
|
||||
}
|
||||
|
||||
result.Metadata["Valid_Key"] = info.Valid
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
// 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 nil, err
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case http.StatusOK:
|
||||
var user UserResponse
|
||||
|
||||
if err := json.Unmarshal(response, &user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &User{
|
||||
ID: user.UserID,
|
||||
Name: user.FirstName,
|
||||
SubscriptionTier: user.Subscription.Tier,
|
||||
SubscriptionStatus: user.Subscription.Status,
|
||||
}, nil
|
||||
case http.StatusUnauthorized:
|
||||
var errorResp ErrorResponse
|
||||
|
||||
if err := json.Unmarshal(response, &errorResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if errorResp.Detail.Status == InvalidAPIKey || errorResp.Detail.Status == NotVerifiable {
|
||||
return nil, errors.New("invalid api key")
|
||||
} else if errorResp.Detail.Status == MissingPermissions {
|
||||
// key is missing user read permissions but is valid
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
default:
|
||||
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 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,
|
||||
Type: "User",
|
||||
Permission: PermissionStrings[UserRead],
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
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
|
||||
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 {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
// dubbings
|
||||
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 !secretInfo.HasPermission(DubbingWrite) {
|
||||
if err := getDebugging(client, key, secretInfo); err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// voices
|
||||
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 {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
// projects
|
||||
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 {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
// pronunciation dictionaries
|
||||
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 {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
// models
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
if err := getModels(client, key, secretInfo); err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
// audio native
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
if err := updateAudioNativeProject(client, key, secretInfo); err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
// workspace
|
||||
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 {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
// audio isolation
|
||||
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
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for all API calls to finish
|
||||
wg.Wait()
|
||||
close(errChan)
|
||||
|
||||
// collect all errors
|
||||
for err := range errChan {
|
||||
aggregatedErrs = append(aggregatedErrs, err.Error())
|
||||
}
|
||||
|
||||
if len(aggregatedErrs) > 0 {
|
||||
return errors.New(strings.Join(aggregatedErrs, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cli print functions
|
||||
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()
|
||||
t.SetOutputMirror(os.Stdout)
|
||||
t.AppendHeader(table.Row{"Permission"})
|
||||
for _, permission := range permissions {
|
||||
t.AppendRow(table.Row{color.GreenString(permission)})
|
||||
}
|
||||
t.Render()
|
||||
}
|
||||
|
||||
func printElevenLabsResources(resources []ElevenLabsResource) {
|
||||
color.Green("\n[i] Resources:")
|
||||
t := table.NewWriter()
|
||||
t.SetOutputMirror(os.Stdout)
|
||||
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()
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
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
|
||||
WorkspaceRead Permission = iota
|
||||
WorkspaceWrite Permission = iota
|
||||
)
|
||||
|
||||
var (
|
||||
PermissionStrings = map[Permission]string{
|
||||
TextToSpeech: "text_to_speech",
|
||||
SpeechToSpeech: "speech_to_speech",
|
||||
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",
|
||||
WorkspaceRead: "workspace_read",
|
||||
WorkspaceWrite: "workspace_write",
|
||||
}
|
||||
|
||||
StringToPermission = map[string]Permission{
|
||||
"text_to_speech": TextToSpeech,
|
||||
"speech_to_speech": SpeechToSpeech,
|
||||
"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_read": WorkspaceRead,
|
||||
"workspace_write": WorkspaceWrite,
|
||||
}
|
||||
|
||||
PermissionIDs = map[Permission]int{
|
||||
TextToSpeech: 1,
|
||||
SpeechToSpeech: 2,
|
||||
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: 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,
|
||||
}
|
||||
)
|
||||
|
||||
// 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")
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,782 @@
|
||||
package elevenlabs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"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: "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",
|
||||
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: "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: "https://api.elevenlabs.io/v1/workspace/invites",
|
||||
}
|
||||
|
||||
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"
|
||||
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
|
||||
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 {
|
||||
ID string `json:"history_item_id"`
|
||||
ModelID string `json:"model_id"`
|
||||
VoiceID string `json:"voice_id"`
|
||||
} `json:"history"`
|
||||
}
|
||||
|
||||
// VoiceResponse is the /voices API response
|
||||
type VoicesResponse struct {
|
||||
Voices []struct {
|
||||
ID string `json:"voice_id"`
|
||||
Name string `json:"name"`
|
||||
Category string `json:"category"`
|
||||
} `json:"voices"`
|
||||
}
|
||||
|
||||
// ProjectsResponse is the /projects API response
|
||||
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"`
|
||||
}
|
||||
|
||||
// PronunciationDictionaries is the /pronunciation-dictionaries API response
|
||||
type PronunciationDictionariesResponse struct {
|
||||
PronunciationDictionaries []struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
} `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"`
|
||||
Name string `json:"name"`
|
||||
AccessLevel string `json:"access_level"`
|
||||
} `json:"agents"`
|
||||
}
|
||||
|
||||
// ConversationResponse is the /conversation API response
|
||||
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]
|
||||
if strings.Contains(apiUrl, "%s") {
|
||||
return fmt.Sprintf(apiUrl, fakeID)
|
||||
}
|
||||
|
||||
return apiUrl
|
||||
}
|
||||
|
||||
// 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, http.NoBody)
|
||||
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
|
||||
}
|
||||
|
||||
// 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, method, contentType, key string, payload []byte) ([]byte, int, error) {
|
||||
// Create request with payload
|
||||
req, err := http.NewRequest(method, 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", contentType)
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case http.StatusOK:
|
||||
var history HistoryResponse
|
||||
|
||||
if err := json.Unmarshal(response, &history); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add history read scope to secret info
|
||||
secretInfo.AppendPermission(PermissionStrings[SpeechHistoryRead])
|
||||
// map resource to secret info
|
||||
for _, historyItem := range history.History {
|
||||
secretInfo.AppendResource(ElevenLabsResource{
|
||||
ID: historyItem.ID,
|
||||
Name: "", // no name
|
||||
Type: "History",
|
||||
Permission: PermissionStrings[SpeechHistoryRead],
|
||||
})
|
||||
}
|
||||
|
||||
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.
|
||||
func deleteHistory(client *http.Client, key string, secretInfo *SecretInfo) error {
|
||||
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(SpeechHistoryWrite), http.MethodDelete, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// add read scope of dubbing to avoid get dubbing api call
|
||||
secretInfo.AppendPermission(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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case http.StatusOK:
|
||||
var voices VoicesResponse
|
||||
|
||||
if err := json.Unmarshal(response, &voices); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add voices read scope to secret info
|
||||
secretInfo.AppendPermission(PermissionStrings[VoicesRead])
|
||||
// map resource to secret info
|
||||
for _, voice := range voices.Voices {
|
||||
secretInfo.AppendResource(ElevenLabsResource{
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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, VoiceDoesNotExist)
|
||||
case http.StatusUnauthorized:
|
||||
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
|
||||
default:
|
||||
return fmt.Errorf("unexpected status code: %d while checking voice write scope", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case http.StatusOK:
|
||||
var projects ProjectsResponse
|
||||
|
||||
if err := json.Unmarshal(response, &projects); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add project read scope to secret info
|
||||
secretInfo.AppendPermission(PermissionStrings[ProjectsRead])
|
||||
// map resource to secret info
|
||||
for _, project := range projects.Projects {
|
||||
secretInfo.AppendResource(ElevenLabsResource{
|
||||
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 accessible
|
||||
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
|
||||
}
|
||||
|
||||
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 accessible
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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.AppendPermission(PermissionStrings[PronunciationDictionariesRead])
|
||||
// map resource to secret info
|
||||
for _, pd := range PDs.PronunciationDictionaries {
|
||||
secretInfo.AppendResource(ElevenLabsResource{
|
||||
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 := makeElevenLabsRequestWithPayload(client, getAPIUrl(PronunciationDictionariesWrite), http.MethodPost,
|
||||
"application/json", 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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.AppendPermission(PermissionStrings[ModelsRead])
|
||||
// map resource to secret info
|
||||
for _, model := range models {
|
||||
secretInfo.AppendResource(ElevenLabsResource{
|
||||
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), http.MethodPost,
|
||||
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.AppendPermission(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(WorkspaceWrite), http.MethodDelete,
|
||||
"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.AppendPermission(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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 := ElevenLabsResource{
|
||||
ID: agent.ID,
|
||||
Name: agent.Name,
|
||||
Type: "Agent",
|
||||
Permission: "", // not binded with any permission
|
||||
Metadata: map[string]string{
|
||||
"access level": agent.AccessLevel,
|
||||
},
|
||||
}
|
||||
secretInfo.AppendResource(resource)
|
||||
// get agent conversations
|
||||
if err := getConversation(client, key, agent.ID, 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, 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.AppendResource(ElevenLabsResource{
|
||||
ID: conversation.ID,
|
||||
Name: "", // no name
|
||||
Type: "Conversation",
|
||||
Permission: "", // not binded with any permission
|
||||
Metadata: map[string]string{
|
||||
"status": conversation.Status,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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.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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
{
|
||||
"AnalyzerType": 6,
|
||||
"Bindings": [
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Ahmed",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/User/b9Rou9mHDmTYd8cdWkg2Yk4P2lq1",
|
||||
"Type": "User",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "user_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Alice",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/Xb7hH8MSUJpSbSDYk0k2",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Aria",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/9BWtsMINqrJLrRacOk9x",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Bill",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/pqHfZKP75CvOlQylNhV4",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Brian",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/nPczCjzI2devNBz1zQrb",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Callum",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/N2lVS1w4EtoT3dr4eOWO",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Charlie",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/IKne3meq5aSn9XLyUdCD",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Charlotte",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/XB0fDUnXU5powFXDhCwa",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Chris",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/iP95p4xoKVk53GoZ742B",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Daniel",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/onwK4e9ZLuTAKqWW03F9",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Eleven English v1",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_monolingual_v1",
|
||||
"Type": "Model",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "models_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Eleven English v2",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_english_sts_v2",
|
||||
"Type": "Model",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "models_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Eleven Flash v2",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_flash_v2",
|
||||
"Type": "Model",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "models_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Eleven Flash v2.5",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_flash_v2_5",
|
||||
"Type": "Model",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "models_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Eleven Multilingual v1",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_v1",
|
||||
"Type": "Model",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "models_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Eleven Multilingual v2",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_v2",
|
||||
"Type": "Model",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "models_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Eleven Multilingual v2",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_sts_v2",
|
||||
"Type": "Model",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "models_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Eleven Turbo v2",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_turbo_v2",
|
||||
"Type": "Model",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "models_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Eleven Turbo v2.5",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_turbo_v2_5",
|
||||
"Type": "Model",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "models_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Eric",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/cjVigY5qzO86Huf0OWal",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "George",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/JBFqnCBsd6RMkjVDRZzb",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Jessica",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/cgSgspJ2msm6clMCkdW9",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Laura",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/FGY2WhTYpPnrIDTdsKH5",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Liam",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/TX3LPaxmHKxFdv7VOQHJ",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Lily",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/pFZP5JQG7iQjIQuC4Bku",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Matilda",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/XrExE9yKIg1WjnnlVkGX",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "River",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/SAz9YHcvj6GT2YYXdXww",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Roger",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/CwhRBWXzGAHq8TQ4Fs17",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Sarah",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/EXAVITQu4vr4xnSDxMaL",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Will",
|
||||
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/bIHbv24MWmeRgasZH58o",
|
||||
"Type": "Voice",
|
||||
"Metadata": {
|
||||
"category": "premade"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "voices_read",
|
||||
"Parent": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"UnboundedResources": null,
|
||||
"Metadata": {
|
||||
"Valid_Key": true
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/bitbucket"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/digitalocean"
|
||||
"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/github"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/gitlab"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/huggingface"
|
||||
@@ -100,5 +101,7 @@ func Run(keyType string, secretInfo SecretInfo) {
|
||||
airtable.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
case "digitalocean":
|
||||
digitalocean.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
case "elevenlabs":
|
||||
elevenlabs.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user