[Feat] Added Dropbox API OAuth2 Token Analyzer (#4080)
* temp * temp * adeded analysis info in dropbox detector * removed unused function * added scope capabilities in terminal results * updated dropbox analyzer expected_output.json * updated dropbox analyzer test expected output * updated dropbox analyzer test expected output
This commit is contained in:
@@ -100,6 +100,7 @@ const (
|
||||
AnalyzerTypeNgrok
|
||||
AnalyzerTypeMux
|
||||
AnalyzerTypePosthog
|
||||
AnalyzerTypeDropbox
|
||||
// Add new items here with AnalyzerType prefix
|
||||
)
|
||||
|
||||
@@ -145,6 +146,7 @@ var analyzerTypeStrings = map[AnalyzerType]string{
|
||||
AnalyzerTypeNgrok: "Ngrok",
|
||||
AnalyzerTypeMux: "Mux",
|
||||
AnalyzerTypePosthog: "Posthog",
|
||||
AnalyzerTypeDropbox: "Dropbox",
|
||||
// Add new mappings here
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
//go:generate generate_permissions permissions.yaml permissions.go dropbox
|
||||
package dropbox
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"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)
|
||||
|
||||
//go:embed scopes.json
|
||||
var scopeConfigJson []byte
|
||||
|
||||
type Analyzer struct {
|
||||
Cfg *config.Config
|
||||
}
|
||||
type PermissionStatus string
|
||||
|
||||
const (
|
||||
StatusGranted PermissionStatus = "Granted"
|
||||
StatusDenied PermissionStatus = "Denied"
|
||||
StatusUnverified PermissionStatus = "Unverified"
|
||||
)
|
||||
|
||||
func (a Analyzer) Type() analyzers.AnalyzerType {
|
||||
return analyzers.AnalyzerTypeDropbox
|
||||
}
|
||||
|
||||
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
|
||||
token, exist := credInfo["token"]
|
||||
if !exist {
|
||||
return nil, errors.New("token not found in credentials info")
|
||||
}
|
||||
|
||||
info, err := AnalyzePermissions(a.Cfg, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return secretInfoToAnalyzerResult(info), nil
|
||||
}
|
||||
|
||||
func AnalyzeAndPrintPermissions(cfg *config.Config, token string) {
|
||||
info, err := AnalyzePermissions(cfg, token)
|
||||
if err != nil {
|
||||
color.Red("[x] Invalid Dropbox Token\n")
|
||||
color.Red("[x] Error : %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if info == nil {
|
||||
color.Red("[x] Error : %s", "No information found")
|
||||
return
|
||||
}
|
||||
|
||||
color.Green("[i] Valid Dropbox OAuth2 Credentials\n")
|
||||
printAccountAndPermissions(info)
|
||||
}
|
||||
|
||||
func AnalyzePermissions(cfg *config.Config, token string) (*secretInfo, error) {
|
||||
// Dropbox API uses POST requests for all requests, so we need to use an unrestricted client
|
||||
client := analyzers.NewAnalyzeClientUnrestricted(cfg)
|
||||
scopeConfigMap, err := getScopeConfigMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secretInfo := &secretInfo{}
|
||||
|
||||
accountInfoPermission := PermissionStrings[AccountInfoRead]
|
||||
for _, perm := range PermissionStrings {
|
||||
scopeDetails := scopeConfigMap.Scopes[perm]
|
||||
status := StatusUnverified
|
||||
if perm == accountInfoPermission {
|
||||
// Account Info Read permission is always enabled
|
||||
status = StatusGranted
|
||||
}
|
||||
secretInfo.Permissions = append(secretInfo.Permissions, accountPermission{
|
||||
Name: perm,
|
||||
Status: status,
|
||||
Actions: scopeDetails.Actions,
|
||||
})
|
||||
}
|
||||
|
||||
if err := populateAccountInfo(client, secretInfo, token); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := testAllPermissions(client, secretInfo, scopeConfigMap, token); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return secretInfo, nil
|
||||
}
|
||||
|
||||
func populateAccountInfo(client *http.Client, info *secretInfo, token string) error {
|
||||
endpoint := "/2/users/get_current_account"
|
||||
body, statusCode, err := callDropboxAPIEndpoint(client, endpoint, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch statusCode {
|
||||
case http.StatusOK:
|
||||
if err := json.Unmarshal([]byte(body), &info.Account); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal account info: %w", err)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("failed to validate scope. Status %d: %s", statusCode, body)
|
||||
}
|
||||
}
|
||||
|
||||
func testAllPermissions(client *http.Client, info *secretInfo, scopeConfigMap *scopeConfig, token string) error {
|
||||
permissionStatuses := make(map[string]PermissionStatus)
|
||||
|
||||
for _, perm := range PermissionStrings {
|
||||
scopeDetails := scopeConfigMap.Scopes[perm]
|
||||
|
||||
if _, ok := permissionStatuses[perm]; ok || scopeDetails.TestEndpoint == "" {
|
||||
// Skip if the scope has already been determined or has no test endpoint
|
||||
continue
|
||||
}
|
||||
|
||||
if perm == PermissionStrings[Openid] {
|
||||
// The OpenID permission can be validated using the "/2/users/get_current_account" endpoint
|
||||
// If the response contains the "email" key, that implies that the "email" permission is also granted
|
||||
// Similar case for the "given_name" key and the "profile" permission
|
||||
body, statusCode, err := callDropboxAPIEndpoint(client, scopeDetails.TestEndpoint, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch statusCode {
|
||||
case http.StatusOK, http.StatusConflict:
|
||||
// The endpoint responds with 409 Conflict if the openid scope
|
||||
// is granted but the email and profile scopes are not granted
|
||||
permissionStatuses[perm] = StatusGranted
|
||||
|
||||
// Check for the "email" key in the response body
|
||||
if strings.Contains(body, "\"email\":") {
|
||||
permissionStatuses[PermissionStrings[Email]] = StatusGranted
|
||||
} else {
|
||||
permissionStatuses[PermissionStrings[Email]] = StatusDenied
|
||||
}
|
||||
|
||||
// Check for the "given_name" key in the response body
|
||||
if strings.Contains(body, "\"given_name\":") {
|
||||
permissionStatuses[PermissionStrings[Profile]] = StatusGranted
|
||||
} else {
|
||||
permissionStatuses[PermissionStrings[Profile]] = StatusDenied
|
||||
}
|
||||
case http.StatusUnauthorized:
|
||||
permissionStatuses[perm] = StatusDenied
|
||||
permissionStatuses[PermissionStrings[Email]] = StatusDenied
|
||||
permissionStatuses[PermissionStrings[Profile]] = StatusDenied
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
isGranted, err := testPermission(client, scopeDetails.TestEndpoint, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isGranted {
|
||||
permissionStatuses[perm] = StatusDenied
|
||||
continue
|
||||
}
|
||||
|
||||
permissionStatuses[perm] = StatusGranted
|
||||
for _, impliedScope := range scopeDetails.ImpliedScopes {
|
||||
permissionStatuses[impliedScope] = StatusGranted
|
||||
}
|
||||
}
|
||||
|
||||
for idx, permission := range info.Permissions {
|
||||
permission.Status = permissionStatuses[permission.Name]
|
||||
info.Permissions[idx] = permission
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testPermission(client *http.Client, testEndpoint string, token string) (bool, error) {
|
||||
body, statusCode, err := callDropboxAPIEndpoint(client, testEndpoint, token)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case http.StatusUnauthorized:
|
||||
return false, nil
|
||||
case http.StatusBadRequest:
|
||||
if strings.Contains(body, "does not have the required scope") {
|
||||
return false, nil
|
||||
}
|
||||
if strings.Contains(body, "your request body is empty") {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, fmt.Errorf("failed to validate scope. Status %d: %s", statusCode, body)
|
||||
}
|
||||
|
||||
func callDropboxAPIEndpoint(client *http.Client, endpoint string, token string) (string, int, error) {
|
||||
baseURL := "https://api.dropboxapi.com"
|
||||
req, err := http.NewRequest(http.MethodPost, baseURL+endpoint, nil)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_, _ = io.Copy(io.Discard, res.Body)
|
||||
_ = res.Body.Close()
|
||||
}()
|
||||
|
||||
bodyBytes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
return string(bodyBytes), res.StatusCode, nil
|
||||
}
|
||||
|
||||
func getScopeConfigMap() (*scopeConfig, error) {
|
||||
var scopeConfigMap scopeConfig
|
||||
if err := json.Unmarshal(scopeConfigJson, &scopeConfigMap); err != nil {
|
||||
return nil, errors.New("failed to unmarshal scopes.json: " + err.Error())
|
||||
}
|
||||
return &scopeConfigMap, nil
|
||||
}
|
||||
|
||||
func secretInfoToAnalyzerResult(info *secretInfo) *analyzers.AnalyzerResult {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
account := info.Account
|
||||
accountID := account.AccountID
|
||||
allPermissions := getValidatedPermissions(info)
|
||||
|
||||
resource := analyzers.Resource{
|
||||
Name: fmt.Sprintf("%s %s", account.Name.GivenName, account.Name.Surname),
|
||||
FullyQualifiedName: accountID,
|
||||
Type: "account",
|
||||
Metadata: map[string]any{
|
||||
"email": account.Email,
|
||||
"emailVerified": account.EmailVerified,
|
||||
"disabled": account.Disabled,
|
||||
"country": account.Country,
|
||||
"accountType": account.AccountType.Tag,
|
||||
},
|
||||
}
|
||||
analyzers.BindAllPermissions(resource, allPermissions...)
|
||||
result := analyzers.AnalyzerResult{
|
||||
AnalyzerType: analyzers.AnalyzerTypeDropbox,
|
||||
Metadata: nil,
|
||||
Bindings: analyzers.BindAllPermissions(resource, allPermissions...),
|
||||
}
|
||||
return &result
|
||||
}
|
||||
|
||||
func getValidatedPermissions(info *secretInfo) []analyzers.Permission {
|
||||
permissions := []analyzers.Permission{}
|
||||
|
||||
for _, permission := range info.Permissions {
|
||||
if permission.Status != StatusGranted {
|
||||
continue
|
||||
}
|
||||
permissions = append(permissions, analyzers.Permission{
|
||||
Value: permission.Name,
|
||||
})
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
func printAccountAndPermissions(info *secretInfo) {
|
||||
color.Yellow("\n[i] Accounts Info:")
|
||||
t1 := table.NewWriter()
|
||||
t1.SetOutputMirror(os.Stdout)
|
||||
t1.AppendHeader(table.Row{"ID", "Name", "Email", "Email Verified", "Disabled", "Country", "Account Type"})
|
||||
emailVerified := "No"
|
||||
disabled := "No"
|
||||
if info.Account.EmailVerified {
|
||||
emailVerified = "Yes"
|
||||
}
|
||||
if info.Account.Disabled {
|
||||
disabled = "Yes"
|
||||
}
|
||||
t1.AppendRow(table.Row{
|
||||
color.GreenString(info.Account.AccountID),
|
||||
color.GreenString(info.Account.Name.GivenName + " " + info.Account.Name.Surname),
|
||||
color.GreenString(info.Account.Email),
|
||||
color.GreenString(emailVerified),
|
||||
color.GreenString(disabled),
|
||||
color.GreenString(info.Account.Country),
|
||||
color.GreenString(info.Account.AccountType.Tag),
|
||||
})
|
||||
t1.SetOutputMirror(os.Stdout)
|
||||
t1.Render()
|
||||
|
||||
color.Yellow("\n[i] Permissions:")
|
||||
t2 := table.NewWriter()
|
||||
t2.AppendHeader(table.Row{"Permission", "Access", "Actions"})
|
||||
|
||||
permissions := info.Permissions
|
||||
for _, permission := range permissions {
|
||||
access := "Denied"
|
||||
permissionStatus := permission.Status
|
||||
if permissionStatus == StatusGranted {
|
||||
access = "Granted"
|
||||
}
|
||||
if permissionStatus == StatusUnverified {
|
||||
access = "Unverified"
|
||||
}
|
||||
for idx, action := range permission.Actions {
|
||||
permissionCell := ""
|
||||
accessCell := ""
|
||||
if idx == 0 {
|
||||
permissionCell = color.GreenString(permission.Name)
|
||||
accessCell = color.GreenString(access)
|
||||
}
|
||||
|
||||
t2.AppendRow(table.Row{
|
||||
permissionCell,
|
||||
accessCell,
|
||||
action,
|
||||
})
|
||||
}
|
||||
t2.AppendSeparator()
|
||||
}
|
||||
|
||||
t2.SetOutputMirror(os.Stdout)
|
||||
t2.Render()
|
||||
fmt.Printf("%s: https://www.dropbox.com/developers/documentation\n\n", color.GreenString("Ref"))
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package dropbox
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
//go:embed expected_output.json
|
||||
var expectedOutput []byte
|
||||
|
||||
func TestAnalyzer_Analyze(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.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)
|
||||
}
|
||||
|
||||
token := testSecrets.MustGetField("DROPBOX")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
secret string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid dropbox credentials",
|
||||
secret: token,
|
||||
want: string(expectedOutput),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := Analyzer{Cfg: &config.Config{}}
|
||||
got, err := a.Analyze(ctx, map[string]string{
|
||||
"token": tt.secret,
|
||||
})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
// marshal the actual result to JSON
|
||||
gotJSON, err := json.Marshal(got)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to JSON: %s", err)
|
||||
}
|
||||
|
||||
fmt.Println(string(gotJSON))
|
||||
|
||||
// compare the JSON strings
|
||||
if string(gotJSON) != string(tt.want) {
|
||||
// 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(tt.want, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to indented JSON: %s", err)
|
||||
}
|
||||
t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"AnalyzerType":40,"Bindings":[{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"[email protected]","emailVerified":true},"Parent":null},"Permission":{"Value":"accounts_info.read","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"[email protected]","emailVerified":true},"Parent":null},"Permission":{"Value":"files.metadata.write","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"[email protected]","emailVerified":true},"Parent":null},"Permission":{"Value":"sharing.read","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"[email protected]","emailVerified":true},"Parent":null},"Permission":{"Value":"contacts.read","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"[email protected]","emailVerified":true},"Parent":null},"Permission":{"Value":"files.content.read","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"[email protected]","emailVerified":true},"Parent":null},"Permission":{"Value":"sharing.write","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"[email protected]","emailVerified":true},"Parent":null},"Permission":{"Value":"contacts.write","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"[email protected]","emailVerified":true},"Parent":null},"Permission":{"Value":"files.metadata.read","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"[email protected]","emailVerified":true},"Parent":null},"Permission":{"Value":"files.content.write","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"[email protected]","emailVerified":true},"Parent":null},"Permission":{"Value":"openid","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"[email protected]","emailVerified":true},"Parent":null},"Permission":{"Value":"file_requests.read","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"[email protected]","emailVerified":true},"Parent":null},"Permission":{"Value":"file_requests.write","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"[email protected]","emailVerified":true},"Parent":null},"Permission":{"Value":"account_info.write","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"[email protected]","emailVerified":true},"Parent":null},"Permission":{"Value":"profile","Parent":null}}],"UnboundedResources":null,"Metadata":null}
|
||||
@@ -0,0 +1,41 @@
|
||||
package dropbox
|
||||
|
||||
type scopeConfig struct {
|
||||
Scopes map[string]scope `json:"scopes"`
|
||||
}
|
||||
|
||||
type scope struct {
|
||||
TestEndpoint string `json:"test_endpoint"`
|
||||
ImpliedScopes []string `json:"implied_scopes"`
|
||||
Actions []string `json:"actions"`
|
||||
}
|
||||
|
||||
type account struct {
|
||||
AccountID string `json:"account_id"`
|
||||
Name name `json:"name"`
|
||||
Email string `json:"email"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Country string `json:"country"`
|
||||
AccountType accountType `json:"account_type"`
|
||||
}
|
||||
|
||||
type accountType struct {
|
||||
Tag string `json:".tag"`
|
||||
}
|
||||
|
||||
type name struct {
|
||||
GivenName string `json:"given_name"`
|
||||
Surname string `json:"surname"`
|
||||
}
|
||||
|
||||
type accountPermission struct {
|
||||
Name string
|
||||
Status PermissionStatus
|
||||
Actions []string
|
||||
}
|
||||
|
||||
type secretInfo struct {
|
||||
Account account
|
||||
Permissions []accountPermission
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package dropbox
|
||||
|
||||
import "errors"
|
||||
|
||||
type Permission int
|
||||
|
||||
const (
|
||||
Invalid Permission = iota
|
||||
AccountInfoWrite Permission = iota
|
||||
AccountInfoRead Permission = iota
|
||||
FilesMetadataWrite Permission = iota
|
||||
FilesMetadataRead Permission = iota
|
||||
FilesContentWrite Permission = iota
|
||||
FilesContentRead Permission = iota
|
||||
SharingWrite Permission = iota
|
||||
SharingRead Permission = iota
|
||||
FileRequestsWrite Permission = iota
|
||||
FileRequestsRead Permission = iota
|
||||
ContactsWrite Permission = iota
|
||||
ContactsRead Permission = iota
|
||||
Openid Permission = iota
|
||||
Profile Permission = iota
|
||||
Email Permission = iota
|
||||
)
|
||||
|
||||
var (
|
||||
PermissionStrings = map[Permission]string{
|
||||
AccountInfoWrite: "account_info.write",
|
||||
AccountInfoRead: "account_info.read",
|
||||
FilesMetadataWrite: "files.metadata.write",
|
||||
FilesMetadataRead: "files.metadata.read",
|
||||
FilesContentWrite: "files.content.write",
|
||||
FilesContentRead: "files.content.read",
|
||||
SharingWrite: "sharing.write",
|
||||
SharingRead: "sharing.read",
|
||||
FileRequestsWrite: "file_requests.write",
|
||||
FileRequestsRead: "file_requests.read",
|
||||
ContactsWrite: "contacts.write",
|
||||
ContactsRead: "contacts.read",
|
||||
Openid: "openid",
|
||||
Profile: "profile",
|
||||
Email: "email",
|
||||
}
|
||||
|
||||
StringToPermission = map[string]Permission{
|
||||
"account_info.write": AccountInfoWrite,
|
||||
"account_info.read": AccountInfoRead,
|
||||
"files.metadata.write": FilesMetadataWrite,
|
||||
"files.metadata.read": FilesMetadataRead,
|
||||
"files.content.write": FilesContentWrite,
|
||||
"files.content.read": FilesContentRead,
|
||||
"sharing.write": SharingWrite,
|
||||
"sharing.read": SharingRead,
|
||||
"file_requests.write": FileRequestsWrite,
|
||||
"file_requests.read": FileRequestsRead,
|
||||
"contacts.write": ContactsWrite,
|
||||
"contacts.read": ContactsRead,
|
||||
"openid": Openid,
|
||||
"profile": Profile,
|
||||
"email": Email,
|
||||
}
|
||||
|
||||
PermissionIDs = map[Permission]int{
|
||||
AccountInfoWrite: 1,
|
||||
AccountInfoRead: 2,
|
||||
FilesMetadataWrite: 3,
|
||||
FilesMetadataRead: 4,
|
||||
FilesContentWrite: 5,
|
||||
FilesContentRead: 6,
|
||||
SharingWrite: 7,
|
||||
SharingRead: 8,
|
||||
FileRequestsWrite: 9,
|
||||
FileRequestsRead: 10,
|
||||
ContactsWrite: 11,
|
||||
ContactsRead: 12,
|
||||
Openid: 13,
|
||||
Profile: 14,
|
||||
Email: 15,
|
||||
}
|
||||
|
||||
IdToPermission = map[int]Permission{
|
||||
1: AccountInfoWrite,
|
||||
2: AccountInfoRead,
|
||||
3: FilesMetadataWrite,
|
||||
4: FilesMetadataRead,
|
||||
5: FilesContentWrite,
|
||||
6: FilesContentRead,
|
||||
7: SharingWrite,
|
||||
8: SharingRead,
|
||||
9: FileRequestsWrite,
|
||||
10: FileRequestsRead,
|
||||
11: ContactsWrite,
|
||||
12: ContactsRead,
|
||||
13: Openid,
|
||||
14: Profile,
|
||||
15: Email,
|
||||
}
|
||||
)
|
||||
|
||||
// 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,16 @@
|
||||
permissions:
|
||||
- account_info.write
|
||||
- account_info.read
|
||||
- files.metadata.write
|
||||
- files.metadata.read
|
||||
- files.content.write
|
||||
- files.content.read
|
||||
- sharing.write
|
||||
- sharing.read
|
||||
- file_requests.write
|
||||
- file_requests.read
|
||||
- contacts.write
|
||||
- contacts.read
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"scopes": {
|
||||
"account_info.write": {
|
||||
"test_endpoint": "/2/account/set_profile_photo",
|
||||
"actions": [
|
||||
"Set a user's profile photo"
|
||||
]
|
||||
},
|
||||
"account_info.read": {
|
||||
"test_endpoint": "/2/account/set_profile_photo",
|
||||
"actions": [
|
||||
"Validate user access token",
|
||||
"Get a list of feature values for the current account",
|
||||
"Get information about the current user's account",
|
||||
"Get the space usage information for the current user's account"
|
||||
]
|
||||
},
|
||||
"files.metadata.write": {
|
||||
"test_endpoint": "/2/file_properties/properties/add",
|
||||
"implied_scopes": [
|
||||
"files.metadata.read"
|
||||
],
|
||||
"actions": [
|
||||
"Add, update or remove property groups associated with files",
|
||||
"Add, update or remove properties associated with files and templates",
|
||||
"Add, update or remove templates associated with a user",
|
||||
"Add or remove tags from items"
|
||||
]
|
||||
},
|
||||
"files.metadata.read": {
|
||||
"test_endpoint": "/2/file_properties/properties/search",
|
||||
"actions": [
|
||||
"Search across property templates for particular property field values",
|
||||
"Get the schema for a specified template",
|
||||
"Get the template identifiers for a team",
|
||||
"Get the metadata for a file or folder",
|
||||
"Get files, revisions, and folder contents",
|
||||
"Monitor for file changes",
|
||||
"Get tags from items",
|
||||
"Get file metadata",
|
||||
"Get user templates",
|
||||
"Get user Paper docs"
|
||||
]
|
||||
},
|
||||
"files.content.write": {
|
||||
"test_endpoint": "/2/files/copy_v2",
|
||||
"implied_scopes": [
|
||||
"files.metadata.read"
|
||||
],
|
||||
"actions": [
|
||||
"Add, update, move, or remove files",
|
||||
"Add, update, move, or remove folders",
|
||||
"Upload file content",
|
||||
"Lock/unlock files for writing",
|
||||
"Restore files to previous versions",
|
||||
"Add, update, or archive Paper docs",
|
||||
"Save URLs to Dropbox"
|
||||
]
|
||||
},
|
||||
"files.content.read": {
|
||||
"test_endpoint": "/2/files/get_file_lock_batch",
|
||||
"actions": [
|
||||
"Export or download files",
|
||||
"Get lock information for files and folders",
|
||||
"Get file previews",
|
||||
"Stream file content",
|
||||
"Get image file thumbnails",
|
||||
"Export or download Paper docs"
|
||||
]
|
||||
},
|
||||
"sharing.write": {
|
||||
"test_endpoint": "/2/sharing/add_file_member",
|
||||
"implied_scopes": [
|
||||
"sharing.read"
|
||||
],
|
||||
"actions": [
|
||||
"Add, update, or remove file members",
|
||||
"Add, update, or remove folder members",
|
||||
"Get status of all asynchronous jobs",
|
||||
"Add, update, or remove shared links",
|
||||
"Share or unshare folders",
|
||||
"Add, update, or remove shared folder access policies",
|
||||
"Mount or unmount folders",
|
||||
"Add or remove users from Paper docs"
|
||||
]
|
||||
},
|
||||
"sharing.read": {
|
||||
"test_endpoint": "/2/sharing/get_file_metadata",
|
||||
"actions": [
|
||||
"Get file metadata",
|
||||
"Get folder metadata",
|
||||
"Get shared link metadata",
|
||||
"Get file members",
|
||||
"Get folder members",
|
||||
"Get shared files",
|
||||
"Get shared folders",
|
||||
"Get mountable shared folders",
|
||||
"Get shared links",
|
||||
"Get information about the user's account",
|
||||
"Get file and folder information for Paper doc",
|
||||
"Get all users with Paper doc access"
|
||||
]
|
||||
},
|
||||
"file_requests.write": {
|
||||
"test_endpoint": "/2/file_requests/update",
|
||||
"implied_scopes": [
|
||||
"file_requests.read"
|
||||
],
|
||||
"actions": [
|
||||
"Add, update, or remove file requests"
|
||||
]
|
||||
},
|
||||
"file_requests.read": {
|
||||
"test_endpoint": "/2/file_requests/list/continue",
|
||||
"actions": [
|
||||
"Get file requests",
|
||||
"Get file request count"
|
||||
]
|
||||
},
|
||||
"contacts.write": {
|
||||
"test_endpoint": "/2/contacts/delete_manual_contacts_batch",
|
||||
"implied_scopes": [
|
||||
"contacts.read"
|
||||
],
|
||||
"actions": [
|
||||
"Remove manually added contacts"
|
||||
]
|
||||
},
|
||||
"contacts.read": {},
|
||||
"openid": {
|
||||
"test_endpoint": "/2/openid/userinfo",
|
||||
"actions": [
|
||||
"Get OpenID Connect user info"
|
||||
]
|
||||
},
|
||||
"profile": {
|
||||
"actions": [
|
||||
"Get name in user info"
|
||||
]
|
||||
},
|
||||
"email": {
|
||||
"actions": [
|
||||
"Get email address in user info"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,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/dropbox"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/elevenlabs"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/fastly"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/figma"
|
||||
@@ -139,5 +140,7 @@ func Run(keyType string, secretInfo SecretInfo) {
|
||||
mux.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"], secretInfo.Parts["secret"])
|
||||
case "posthog":
|
||||
posthog.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
case "dropbox":
|
||||
dropbox.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
isVerified, verificationErr := verifyDropboxToken(ctx, client, key)
|
||||
s1.Verified = isVerified
|
||||
s1.SetVerificationError(verificationErr)
|
||||
if s1.Verified {
|
||||
s1.AnalysisInfo = map[string]string{"token": key}
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
|
||||
Reference in New Issue
Block a user