[Feat] implementation Notion analyzer (#3869)
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
### Description: Since `Notion.co` allows to enable [capabilities](https://developers.notion.com/reference/capabilities) against integration token, It is a good candidate to build an analyzer for it.  This PR implements notion analyzer along with its integration test. ### Checklist: * [ ] Tests passing (`make test-community`)? * [x] Lint passing (`make lint` this requires [golangci-lint](https://golangci-lint.run/welcome/install/#local-installation))?
This commit is contained in:
@@ -82,6 +82,7 @@ const (
|
||||
AnalyzerTypeStripe
|
||||
AnalyzerTypeTwilio
|
||||
AnalyzerTypePrivateKey
|
||||
AnalyzerTypeNotion
|
||||
// Add new items here with AnalyzerType prefix
|
||||
)
|
||||
|
||||
@@ -110,6 +111,7 @@ var analyzerTypeStrings = map[AnalyzerType]string{
|
||||
AnalyzerTypeStripe: "Stripe",
|
||||
AnalyzerTypeTwilio: "Twilio",
|
||||
AnalyzerTypePrivateKey: "PrivateKey",
|
||||
AnalyzerTypeNotion: "Notion",
|
||||
// Add new mappings here
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"AnalyzerType":22,"Bindings":[{"Resource":{"Name":"hooman","FullyQualifiedName":"notion.so/bot/62faeec3-a948-4dd4-90ae-426e0b192902","Type":"bot","Metadata":{"workspace":"hoomanit"},"Parent":null},"Permission":{"Value":"insert_content","Parent":null}},{"Resource":{"Name":"hooman","FullyQualifiedName":"notion.so/bot/62faeec3-a948-4dd4-90ae-426e0b192902","Type":"bot","Metadata":{"workspace":"hoomanit"},"Parent":null},"Permission":{"Value":"read_content","Parent":null}},{"Resource":{"Name":"hooman","FullyQualifiedName":"notion.so/bot/62faeec3-a948-4dd4-90ae-426e0b192902","Type":"bot","Metadata":{"workspace":"hoomanit"},"Parent":null},"Permission":{"Value":"read_users_with_email","Parent":null}},{"Resource":{"Name":"hooman","FullyQualifiedName":"notion.so/bot/62faeec3-a948-4dd4-90ae-426e0b192902","Type":"bot","Metadata":{"workspace":"hoomanit"},"Parent":null},"Permission":{"Value":"update_content","Parent":null}}],"UnboundedResources":[{"Name":"hooman","FullyQualifiedName":"notion.so/person/3d0600fa-fa18-427d-8abc-58b662f0d209","Type":"person","Metadata":{"email":"[email protected]"},"Parent":null}],"Metadata":null}
|
||||
@@ -0,0 +1,389 @@
|
||||
//go:generate generate_permissions permissions.yaml permissions.go notion
|
||||
|
||||
package notion
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jedib0t/go-pretty/table"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
var _ analyzers.Analyzer = (*Analyzer)(nil)
|
||||
|
||||
type Analyzer struct {
|
||||
Cfg *config.Config
|
||||
}
|
||||
|
||||
func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeNotion }
|
||||
|
||||
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
|
||||
key, ok := credInfo["key"]
|
||||
if !ok {
|
||||
return nil, errors.New("missing key in credInfo")
|
||||
}
|
||||
info, err := AnalyzePermissions(a.Cfg, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return secretInfoToAnalyzerResult(info), nil
|
||||
}
|
||||
|
||||
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
result := analyzers.AnalyzerResult{
|
||||
AnalyzerType: analyzers.AnalyzerTypeNotion,
|
||||
Metadata: nil,
|
||||
Bindings: make([]analyzers.Binding, len(info.Permissions)),
|
||||
UnboundedResources: make([]analyzers.Resource, 0, len(info.WorkspaceUsers)),
|
||||
}
|
||||
|
||||
resource := analyzers.Resource{
|
||||
Name: info.Bot.Name,
|
||||
FullyQualifiedName: "notion.so/bot/" + info.Bot.Id,
|
||||
Type: info.Bot.Type,
|
||||
Metadata: map[string]interface{}{
|
||||
"workspace": info.Bot.GetWorkspaceName(),
|
||||
},
|
||||
}
|
||||
|
||||
for idx, permission := range info.Permissions {
|
||||
result.Bindings[idx] = analyzers.Binding{
|
||||
Resource: resource,
|
||||
Permission: analyzers.Permission{
|
||||
Value: permission,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// We can find list of users in the current workspace
|
||||
// if the API key has read_user permission, so these can be
|
||||
// unbounded resources
|
||||
for _, user := range info.WorkspaceUsers {
|
||||
if info.Bot.Id == user.Id {
|
||||
// Skip the bot itself
|
||||
continue
|
||||
}
|
||||
unboundresource := analyzers.Resource{
|
||||
Name: user.Name,
|
||||
FullyQualifiedName: fmt.Sprintf("notion.so/%s/%s", user.Type, user.Id),
|
||||
Type: user.Type, // person or bot
|
||||
}
|
||||
if user.Person.Email != "" {
|
||||
unboundresource.Metadata = map[string]interface{}{
|
||||
"email": user.Person.Email,
|
||||
}
|
||||
}
|
||||
|
||||
result.UnboundedResources = append(result.UnboundedResources, unboundresource)
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
//go:embed scopes.json
|
||||
var scopesConfig []byte
|
||||
|
||||
type HttpStatusTest struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
Method string `json:"method"`
|
||||
Payload interface{} `json:"payload"`
|
||||
ValidStatuses []int `json:"valid_status_code"`
|
||||
InvalidStatuses []int `json:"invalid_status_code"`
|
||||
}
|
||||
|
||||
func StatusContains(status int, vals []int) bool {
|
||||
for _, v := range vals {
|
||||
if status == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *HttpStatusTest) RunTest(cfg *config.Config, headers map[string]string) (bool, error) {
|
||||
// If body data, marshal to JSON
|
||||
var data io.Reader
|
||||
if h.Payload != nil {
|
||||
jsonData, err := json.Marshal(h.Payload)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
data = bytes.NewBuffer(jsonData)
|
||||
}
|
||||
|
||||
// Create new HTTP request
|
||||
client := analyzers.NewAnalyzeClientUnrestricted(cfg)
|
||||
req, err := http.NewRequest(h.Method, h.Endpoint, data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Add custom headers if provided
|
||||
for key, value := range headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
// Execute HTTP Request
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check response status code
|
||||
switch {
|
||||
case StatusContains(resp.StatusCode, h.ValidStatuses):
|
||||
return true, nil
|
||||
case StatusContains(resp.StatusCode, h.InvalidStatuses):
|
||||
return false, nil
|
||||
default:
|
||||
return false, errors.New("error checking response status code")
|
||||
}
|
||||
}
|
||||
|
||||
type Scope struct {
|
||||
Name string `json:"name"`
|
||||
HttpTest HttpStatusTest `json:"test"`
|
||||
}
|
||||
|
||||
func readInScopes() ([]Scope, error) {
|
||||
var scopes []Scope
|
||||
if err := json.Unmarshal(scopesConfig, &scopes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return scopes, nil
|
||||
}
|
||||
|
||||
func getPermissions(cfg *config.Config, key string) ([]string, error) {
|
||||
scopes, err := readInScopes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading in scopes: %w", err)
|
||||
}
|
||||
|
||||
permissions := make([]string, 0, len(scopes))
|
||||
for _, scope := range scopes {
|
||||
status, err := scope.HttpTest.RunTest(cfg, map[string]string{"Authorization": "Bearer " + key, "Notion-Version": "2022-06-28"})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("running test: %w", err)
|
||||
}
|
||||
if status {
|
||||
permissions = append(permissions, scope.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
type SecretInfo struct {
|
||||
Bot *bot
|
||||
WorkspaceUsers []user
|
||||
Permissions []string
|
||||
}
|
||||
|
||||
type user struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Person struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
}
|
||||
|
||||
type bot struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Bot struct {
|
||||
Owner *struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
WorkspaceName string `json:"workspace_name"`
|
||||
} `json:"bot"`
|
||||
}
|
||||
|
||||
func (b *bot) GetWorkspaceName() string {
|
||||
return b.Bot.WorkspaceName
|
||||
}
|
||||
|
||||
func (b *bot) OwnedBy() string {
|
||||
if b.Bot.Owner != nil {
|
||||
return b.Bot.Owner.Type
|
||||
}
|
||||
return "N/A"
|
||||
}
|
||||
|
||||
func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
|
||||
info, err := AnalyzePermissions(cfg, key)
|
||||
if err != nil {
|
||||
color.Red("[x] Error : %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
color.Green("[!] Valid Notion API key\n\n")
|
||||
|
||||
color.Green("[i] Bot: %s (%s)\n", info.Bot.Name, info.Bot.Id)
|
||||
color.Green("[i] Bot Owned By: %s\n", info.Bot.OwnedBy())
|
||||
|
||||
if info.Bot.GetWorkspaceName() != "" {
|
||||
color.Green("[i] Workspace: %s\n\n", info.Bot.GetWorkspaceName())
|
||||
}
|
||||
|
||||
printPermissions(info.Permissions)
|
||||
if len(info.WorkspaceUsers) > 0 {
|
||||
printUsers(info.WorkspaceUsers)
|
||||
}
|
||||
color.Yellow("\n[i] Expires: Never")
|
||||
|
||||
}
|
||||
|
||||
func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
|
||||
permissions := make([]string, 0)
|
||||
|
||||
client := analyzers.NewAnalyzeClient(cfg)
|
||||
|
||||
bot, err := getBotInfo(client, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credPermissions, err := getPermissions(cfg, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
permissions = append(permissions, credPermissions...)
|
||||
|
||||
users, err := getWorkspaceUsers(client, key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting user permission: %s", err.Error())
|
||||
}
|
||||
|
||||
// check if email is returned in users to determine permission
|
||||
for _, user := range users {
|
||||
if user.Type == "person" {
|
||||
if user.Person.Email == "" {
|
||||
permissions = append(permissions, PermissionStrings[ReadUsersWithoutEmail])
|
||||
} else {
|
||||
permissions = append(permissions, PermissionStrings[ReadUsersWithEmail])
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return &SecretInfo{
|
||||
Bot: bot,
|
||||
Permissions: permissions,
|
||||
WorkspaceUsers: users,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func printPermissions(permissions []string) {
|
||||
color.Yellow("[i] Permissions:")
|
||||
t := table.NewWriter()
|
||||
t.SetOutputMirror(os.Stdout)
|
||||
t.AppendHeader(table.Row{"Permission"})
|
||||
for _, permission := range permissions {
|
||||
t.AppendRow(table.Row{color.GreenString(permission)})
|
||||
}
|
||||
t.Render()
|
||||
}
|
||||
|
||||
func printUsers(users []user) {
|
||||
color.Yellow("\n[i] Workspace Users:")
|
||||
t := table.NewWriter()
|
||||
t.SetOutputMirror(os.Stdout)
|
||||
t.AppendHeader(table.Row{"ID", "Name", "Type", "Email"})
|
||||
for _, user := range users {
|
||||
t.AppendRow(table.Row{color.GreenString(user.Id), color.GreenString(user.Name), color.GreenString(user.Type), color.GreenString(user.Person.Email)})
|
||||
}
|
||||
t.Render()
|
||||
}
|
||||
|
||||
func getBotInfo(client *http.Client, key string) (*bot, error) {
|
||||
// Create new HTTP request
|
||||
req, err := http.NewRequest(http.MethodGet, "https://api.notion.com/v1/users/me", http.NoBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add custom headers if provided
|
||||
req.Header.Set("Authorization", "Bearer "+key)
|
||||
req.Header.Set("Notion-Version", "2022-06-28")
|
||||
|
||||
// Execute HTTP Request
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
me := &bot{}
|
||||
err = json.NewDecoder(resp.Body).Decode(me)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return me, nil
|
||||
case http.StatusUnauthorized:
|
||||
return nil, errors.New("invalid API key")
|
||||
default:
|
||||
return nil, errors.New("error getting bot info")
|
||||
}
|
||||
}
|
||||
|
||||
// Decode response body
|
||||
type usersResponse struct {
|
||||
Results []user `json:"results"`
|
||||
}
|
||||
|
||||
func getWorkspaceUsers(client *http.Client, key string) ([]user, error) {
|
||||
// Create new HTTP request
|
||||
req, err := http.NewRequest(http.MethodGet, "https://api.notion.com/v1/users", http.NoBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add custom headers if provided
|
||||
req.Header.Set("Authorization", "Bearer "+key)
|
||||
req.Header.Set("Notion-Version", "2022-06-28")
|
||||
|
||||
// Execute HTTP Request
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
response := &usersResponse{}
|
||||
err = json.NewDecoder(resp.Body).Decode(response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.Results, nil
|
||||
case http.StatusUnauthorized:
|
||||
return nil, errors.New("invalid API key")
|
||||
case http.StatusForbidden:
|
||||
return nil, nil // no permission
|
||||
case http.StatusNotFound:
|
||||
return nil, errors.New("workspace not found")
|
||||
default:
|
||||
return nil, errors.New("error checking user permissions")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package notion
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
//go:embed expected_output.json
|
||||
var expectedOutput []byte
|
||||
|
||||
func TestAnalyzer_Analyze(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
|
||||
defer cancel()
|
||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
|
||||
if err != nil {
|
||||
t.Fatalf("could not get test secrets from GCP: %s", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
want string // JSON string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid notion key",
|
||||
key: testSecrets.MustGetField("NOTION_TOKEN"),
|
||||
want: string(expectedOutput),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := Analyzer{Cfg: &config.Config{}}
|
||||
got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
// bindings need to be in the same order to be comparable
|
||||
sortBindings(got.Bindings)
|
||||
|
||||
// Marshal the actual result to JSON
|
||||
gotJSON, err := json.Marshal(got)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Parse the expected JSON string
|
||||
var wantObj analyzers.AnalyzerResult
|
||||
if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
|
||||
t.Fatalf("could not unmarshal want JSON string: %s", err)
|
||||
}
|
||||
|
||||
// bindings need to be in the same order to be comparable
|
||||
sortBindings(wantObj.Bindings)
|
||||
|
||||
// Marshal the expected result to JSON (to normalize)
|
||||
wantJSON, err := json.Marshal(wantObj)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Compare the JSON strings
|
||||
if string(gotJSON) != string(wantJSON) {
|
||||
// Pretty-print both JSON strings for easier comparison
|
||||
var gotIndented, wantIndented []byte
|
||||
gotIndented, err = json.MarshalIndent(got, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to indented JSON: %s", err)
|
||||
}
|
||||
wantIndented, err = json.MarshalIndent(wantObj, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to indented JSON: %s", err)
|
||||
}
|
||||
t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to sort bindings
|
||||
func sortBindings(bindings []analyzers.Binding) {
|
||||
sort.SliceStable(bindings, func(i, j int) bool {
|
||||
if bindings[i].Resource.Name == bindings[j].Resource.Name {
|
||||
return bindings[i].Permission.Value < bindings[j].Permission.Value
|
||||
}
|
||||
return bindings[i].Resource.Name < bindings[j].Resource.Name
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package notion
|
||||
|
||||
import "errors"
|
||||
|
||||
type Permission int
|
||||
|
||||
const (
|
||||
Invalid Permission = iota
|
||||
ReadContent Permission = iota
|
||||
UpdateContent Permission = iota
|
||||
InsertContent Permission = iota
|
||||
ReadComments Permission = iota
|
||||
InsertComments Permission = iota
|
||||
ReadUsersWithEmail Permission = iota
|
||||
ReadUsersWithoutEmail Permission = iota
|
||||
)
|
||||
|
||||
var (
|
||||
PermissionStrings = map[Permission]string{
|
||||
ReadContent: "read_content",
|
||||
UpdateContent: "update_content",
|
||||
InsertContent: "insert_content",
|
||||
ReadComments: "read_comments",
|
||||
InsertComments: "insert_comments",
|
||||
ReadUsersWithEmail: "read_users_with_email",
|
||||
ReadUsersWithoutEmail: "read_users_without_email",
|
||||
}
|
||||
|
||||
StringToPermission = map[string]Permission{
|
||||
"read_content": ReadContent,
|
||||
"update_content": UpdateContent,
|
||||
"insert_content": InsertContent,
|
||||
"read_comments": ReadComments,
|
||||
"insert_comments": InsertComments,
|
||||
"read_users_with_email": ReadUsersWithEmail,
|
||||
"read_users_without_email": ReadUsersWithoutEmail,
|
||||
}
|
||||
|
||||
PermissionIDs = map[Permission]int{
|
||||
ReadContent: 1,
|
||||
UpdateContent: 2,
|
||||
InsertContent: 3,
|
||||
ReadComments: 4,
|
||||
InsertComments: 5,
|
||||
ReadUsersWithEmail: 6,
|
||||
ReadUsersWithoutEmail: 7,
|
||||
}
|
||||
|
||||
IdToPermission = map[int]Permission{
|
||||
1: ReadContent,
|
||||
2: UpdateContent,
|
||||
3: InsertContent,
|
||||
4: ReadComments,
|
||||
5: InsertComments,
|
||||
6: ReadUsersWithEmail,
|
||||
7: ReadUsersWithoutEmail,
|
||||
}
|
||||
)
|
||||
|
||||
// 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,8 @@
|
||||
permissions:
|
||||
- read_content
|
||||
- update_content
|
||||
- insert_content
|
||||
- read_comments
|
||||
- insert_comments
|
||||
- read_users_with_email
|
||||
- read_users_without_email
|
||||
@@ -0,0 +1,47 @@
|
||||
[
|
||||
{
|
||||
"name": "read_content",
|
||||
"test": {
|
||||
"endpoint": "https://api.notion.com/v1/pages/`nowaythiscanexist",
|
||||
"method": "GET",
|
||||
"valid_status_code": [400],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "update_content",
|
||||
"test": {
|
||||
"endpoint": "https://api.notion.com/v1/pages/`nowaythiscanexist",
|
||||
"method": "PATCH",
|
||||
"valid_status_code": [400],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "insert_content",
|
||||
"test": {
|
||||
"endpoint": "https://api.notion.com/v1/pages",
|
||||
"method": "POST",
|
||||
"valid_status_code": [400],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "read_comments",
|
||||
"test": {
|
||||
"endpoint": "https://api.notion.com/v1/comments",
|
||||
"method": "GET",
|
||||
"valid_status_code": [400],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "insert_comments",
|
||||
"test": {
|
||||
"endpoint": "https://api.notion.com/v1/comments",
|
||||
"method": "POST",
|
||||
"valid_status_code": [400],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airbrake"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/asana"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/bitbucket"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mailchimp"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mailgun"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mysql"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/notion"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/openai"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/opsgenie"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/postgres"
|
||||
@@ -85,6 +87,8 @@ func Run(keyType string, secretInfo SecretInfo) {
|
||||
opsgenie.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
case "privatekey":
|
||||
privatekey.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
case "notion":
|
||||
notion.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
case "dockerhub":
|
||||
dockerhub.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["username"], secretInfo.Parts["pat"])
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
// Notion returns 401 for all non-valid keys, thus 403 indicates it has fine-tuned permissions,
|
||||
// /v1/search, /v1/databases/*, etc. may work.
|
||||
s1.Verified = true
|
||||
s1.AnalysisInfo = map[string]string{"key": resMatch}
|
||||
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user