diff --git a/USAGE.md b/USAGE.md index e15c419d..c87fb0f8 100644 --- a/USAGE.md +++ b/USAGE.md @@ -41,7 +41,8 @@ api_key_vault_cmd = command arg arg ... (space-separated, no shell syntax) api_url = https://api.wakatime.com/api/v1 hide_file_names = false hide_project_names = false -hide_branch_names = +hide_branch_names = false +hide_dependencies = false hide_project_folder = false exclude = ^COMMIT_EDITMSG$ @@ -95,6 +96,7 @@ some/submodule/name = new project name | hide_file_names | Obfuscate filenames. Will not send file names to api. | _bool_;_list_ | `false` | | hide_project_names | Obfuscate project names. When a project folder is detected instead of using the folder name as the project, a `.wakatime-project file` is created with a random project name. | _bool_;_list_ | `false` | | hide_branch_names | Obfuscate branch names. Will not send revision control branch names to api. | _bool_;_list_ | `false` | +| hide_dependencies | Prevent sending imports/libraries/dependencies used in currently focused file to the api. | _bool_;_list_ | `false` | | hide_project_folder | When set, send the file's path relative to the project folder. For ex: `/User/me/projects/bar/src/file.ts` is sent as `src/file.ts` so the server never sees the full path. When the project folder cannot be detected, only the file name is sent. For ex: `file.ts`. | _bool_ | `false` | | exclude | Filename patterns to exclude from logging. POSIX regex syntax. | _bool_;_list_ | | | include | Filename patterns to log. When used in combination with `exclude`, files matching `include` will still be logged. POSIX regex syntax | _bool_;_list_ | | @@ -111,7 +113,7 @@ some/submodule/name = new project name | hostname | Optional name of local machine. By default, auto-detects the local machine’s hostname. | _string_ | | | log_file | Optional log file path. | _filepath_ | `~/.wakatime/wakatime.log` | | import_cfg | Optional path to another wakatime.cfg file to import. If set it will overwrite values loaded from $WAKATIME_HOME/.wakatime.cfg file. | _filepath_ | | -| metrics | When set, collects metrics usage in `~/.wakatime/metrics` folder. For further reference visit . | _bool_ | `false` | +| metrics | When set, collects metrics usage in '~/.wakatime/metrics' folder. For further reference visit . | _bool_ | `false` | | guess_language | When `true`, enables detecting programming language from file contents. | _bool_ | `false` | ### Project Map Section @@ -142,9 +144,9 @@ However, if an api key exists in your `~/.wakatime.cfg` file then it takes prece ### Git Section -| option | description | type | default value | -| --- | --- | --- | --- | -| submodules_disabled | It will be matched against the submodule path and if matching, will skip it. | _bool_;_list_ | false | +| option | description | type | default value | +| --- | --- | --- | --- | +| submodules_disabled | It will be matched against the submodule path and if matching, will skip it. | _bool_;_list_ | false | ### Git Submodule Project Map Section diff --git a/cmd/fileexperts/fileexperts.go b/cmd/fileexperts/fileexperts.go index 5c58b88b..88522a19 100644 --- a/cmd/fileexperts/fileexperts.go +++ b/cmd/fileexperts/fileexperts.go @@ -131,10 +131,11 @@ func initHandleOptions(params paramscmd.Params) []heartbeat.HandleOption { ExcludeUnknownProject: params.Heartbeat.Filter.ExcludeUnknownProject, }), heartbeat.WithSanitization(heartbeat.SanitizeConfig{ - BranchPatterns: params.Heartbeat.Sanitize.HideBranchNames, - FilePatterns: params.Heartbeat.Sanitize.HideFileNames, - HideProjectFolder: params.Heartbeat.Sanitize.HideProjectFolder, - ProjectPatterns: params.Heartbeat.Sanitize.HideProjectNames, + BranchPatterns: params.Heartbeat.Sanitize.HideBranchNames, + DependencyPatterns: params.Heartbeat.Sanitize.HideDependencies, + FilePatterns: params.Heartbeat.Sanitize.HideFileNames, + HideProjectFolder: params.Heartbeat.Sanitize.HideProjectFolder, + ProjectPatterns: params.Heartbeat.Sanitize.HideProjectNames, }), fileexperts.WithValidation(), filter.WithLengthValidator(), diff --git a/cmd/heartbeat/heartbeat.go b/cmd/heartbeat/heartbeat.go index 66ead426..24618388 100644 --- a/cmd/heartbeat/heartbeat.go +++ b/cmd/heartbeat/heartbeat.go @@ -326,10 +326,11 @@ func initHandleOptions(params paramscmd.Params) []heartbeat.HandleOption { ExcludeUnknownProject: params.Heartbeat.Filter.ExcludeUnknownProject, }), heartbeat.WithSanitization(heartbeat.SanitizeConfig{ - BranchPatterns: params.Heartbeat.Sanitize.HideBranchNames, - FilePatterns: params.Heartbeat.Sanitize.HideFileNames, - HideProjectFolder: params.Heartbeat.Sanitize.HideProjectFolder, - ProjectPatterns: params.Heartbeat.Sanitize.HideProjectNames, + BranchPatterns: params.Heartbeat.Sanitize.HideBranchNames, + DependencyPatterns: params.Heartbeat.Sanitize.HideDependencies, + FilePatterns: params.Heartbeat.Sanitize.HideFileNames, + HideProjectFolder: params.Heartbeat.Sanitize.HideProjectFolder, + ProjectPatterns: params.Heartbeat.Sanitize.HideProjectNames, }), remote.WithCleanup(), filter.WithLengthValidator(), diff --git a/cmd/heartbeat/heartbeat_test.go b/cmd/heartbeat/heartbeat_test.go index 74c889f0..38c8dda2 100644 --- a/cmd/heartbeat/heartbeat_test.go +++ b/cmd/heartbeat/heartbeat_test.go @@ -1061,7 +1061,12 @@ func TestSendHeartbeats_ObfuscateProject(t *testing.T) { lines, err := project.ReadFile(ctx, filepath.Join(fp, "wakatime-cli", ".wakatime-project"), 1) require.NoError(t, err) - expectedBodyStr := fmt.Sprintf(string(expectedBody), entity.Entity, lines[0], heartbeat.UserAgent(ctx, plugin)) + expectedBodyStr := fmt.Sprintf( + string(expectedBody), + entity.Entity, + lines[0], + heartbeat.UserAgent(ctx, plugin), + ) assert.True(t, strings.HasSuffix(entity.Entity, "src/pkg/file.go")) assert.JSONEq(t, expectedBodyStr, string(body)) diff --git a/cmd/heartbeat/testdata/api_heartbeats_request_template_obfuscated_project.json b/cmd/heartbeat/testdata/api_heartbeats_request_template_obfuscated_project.json index 0f445e4a..1cb5a93b 100644 --- a/cmd/heartbeat/testdata/api_heartbeats_request_template_obfuscated_project.json +++ b/cmd/heartbeat/testdata/api_heartbeats_request_template_obfuscated_project.json @@ -1,5 +1,6 @@ [ { + "branch": "master", "category": "debugging", "entity": "%s", "is_write": true, diff --git a/cmd/offline/offline.go b/cmd/offline/offline.go index f7c68ed1..eb69c466 100644 --- a/cmd/offline/offline.go +++ b/cmd/offline/offline.go @@ -166,10 +166,11 @@ func initHandleOptions(params paramscmd.Params) []heartbeat.HandleOption { ExcludeUnknownProject: params.Heartbeat.Filter.ExcludeUnknownProject, }), heartbeat.WithSanitization(heartbeat.SanitizeConfig{ - BranchPatterns: params.Heartbeat.Sanitize.HideBranchNames, - FilePatterns: params.Heartbeat.Sanitize.HideFileNames, - HideProjectFolder: params.Heartbeat.Sanitize.HideProjectFolder, - ProjectPatterns: params.Heartbeat.Sanitize.HideProjectNames, + BranchPatterns: params.Heartbeat.Sanitize.HideBranchNames, + DependencyPatterns: params.Heartbeat.Sanitize.HideDependencies, + FilePatterns: params.Heartbeat.Sanitize.HideFileNames, + HideProjectFolder: params.Heartbeat.Sanitize.HideProjectFolder, + ProjectPatterns: params.Heartbeat.Sanitize.HideProjectNames, }), remote.WithCleanup(), filter.WithLengthValidator(), diff --git a/cmd/params/params.go b/cmd/params/params.go index 647506e3..4c979af3 100644 --- a/cmd/params/params.go +++ b/cmd/params/params.go @@ -154,6 +154,7 @@ type ( // SanitizeParams params for heartbeat sanitization. SanitizeParams struct { HideBranchNames []regex.Regex + HideDependencies []regex.Regex HideFileNames []regex.Regex HideProjectFolder bool HideProjectNames []regex.Regex @@ -555,6 +556,22 @@ func loadSanitizeParams(ctx context.Context, v *viper.Viper) (SanitizeParams, er ) } + // hide dependencies + hideDependenciesStr := vipertools.FirstNonEmptyString( + v, + "hide-dependencies", + "settings.hide_dependencies", + ) + + hideDependenciesPatterns, err := parseBoolOrRegexList(ctx, hideDependenciesStr) + if err != nil { + return SanitizeParams{}, fmt.Errorf( + "failed to parse regex hide dependencies param %q: %s", + hideDependenciesStr, + err, + ) + } + // hide project names hideProjectNamesStr := vipertools.FirstNonEmptyString( v, @@ -595,6 +612,7 @@ func loadSanitizeParams(ctx context.Context, v *viper.Viper) (SanitizeParams, er return SanitizeParams{ HideBranchNames: hideBranchNamesPatterns, + HideDependencies: hideDependenciesPatterns, HideFileNames: hideFileNamesPatterns, HideProjectFolder: vipertools.FirstNonEmptyBool(v, "hide-project-folder", "settings.hide_project_folder"), HideProjectNames: hideProjectNamesPatterns, @@ -1146,11 +1164,12 @@ func (p ProjectParams) String() string { func (p SanitizeParams) String() string { return fmt.Sprintf( "hide branch names: '%s', hide project folder: %t, hide file names: '%s',"+ - " hide project names: '%s', project path override: '%s'", + " hide project names: '%s', hide dependencies: '%s', project path override: '%s'", p.HideBranchNames, p.HideProjectFolder, p.HideFileNames, p.HideProjectNames, + p.HideDependencies, p.ProjectPathOverride, ) } diff --git a/cmd/params/params_test.go b/cmd/params/params_test.go index 7baf165e..8edcd0b4 100644 --- a/cmd/params/params_test.go +++ b/cmd/params/params_test.go @@ -1118,7 +1118,7 @@ func TestLoadHeartbeatParams_SanitizeParams_HideBranchNames_FlagTakesPrecedence( func TestLoadHeartbeatParams_SanitizeParams_HideBranchNames_ConfigTakesPrecedence(t *testing.T) { v := viper.New() v.Set("entity", "/path/to/file") - v.Set("settings.hide_branch_names", "true") + v.Set("settings.hide_branch_names", true) v.Set("settings.hide_branchnames", "ignored") v.Set("settings.hidebranchnames", "ignored") @@ -1133,7 +1133,7 @@ func TestLoadHeartbeatParams_SanitizeParams_HideBranchNames_ConfigTakesPrecedenc func TestLoadHeartbeatParams_SanitizeParams_HideBranchNames_ConfigDeprecatedOneTakesPrecedence(t *testing.T) { v := viper.New() v.Set("entity", "/path/to/file") - v.Set("settings.hide_branchnames", "true") + v.Set("settings.hide_branchnames", true) v.Set("settings.hidebranchnames", "ignored") params, err := cmdparams.LoadHeartbeatParams(context.Background(), v) @@ -1147,7 +1147,7 @@ func TestLoadHeartbeatParams_SanitizeParams_HideBranchNames_ConfigDeprecatedOneT func TestLoadHeartbeatParams_SanitizeParams_HideBranchNames_ConfigDeprecatedTwo(t *testing.T) { v := viper.New() v.Set("entity", "/path/to/file") - v.Set("settings.hidebranchnames", "true") + v.Set("settings.hidebranchnames", true) params, err := cmdparams.LoadHeartbeatParams(context.Background(), v) require.NoError(t, err) @@ -1186,6 +1186,163 @@ func TestLoadHeartbeatParams_SanitizeParams_HideBranchNames_InvalidRegex(t *test assert.Contains(t, string(output), "failed to compile regex pattern \\\"(?i)[0-9+\\\", it will be ignored") } +func TestLoadHeartbeatParams_SanitizeParams_HideDependencies_Flag(t *testing.T) { + v := viper.New() + v.Set("entity", "/path/to/file") + v.Set("settings.hide_dependencies", true) + + params, err := cmdparams.LoadHeartbeatParams(context.Background(), v) + require.NoError(t, err) + + assert.Equal(t, cmdparams.SanitizeParams{ + HideDependencies: []regex.Regex{regex.NewRegexpWrap(regexp.MustCompile(".*"))}, + }, params.Sanitize) +} + +func TestLoadHeartbeatParams_SanitizeParams_HideDependencies_True(t *testing.T) { + ctx := context.Background() + + tests := map[string]string{ + "lowercase": "true", + "uppercase": "TRUE", + "first uppercase": "True", + } + + for name, viperValue := range tests { + t.Run(name, func(t *testing.T) { + v := viper.New() + v.Set("entity", "/path/to/file") + v.Set("hide-dependencies", viperValue) + + params, err := cmdparams.LoadHeartbeatParams(ctx, v) + require.NoError(t, err) + + assert.Equal(t, cmdparams.SanitizeParams{ + HideDependencies: []regex.Regex{regex.NewRegexpWrap(regexp.MustCompile(".*"))}, + }, params.Sanitize) + }) + } +} + +func TestLoadHeartbeatParams_SanitizeParams_HideDependencies_False(t *testing.T) { + ctx := context.Background() + + tests := map[string]string{ + "lowercase": "false", + "uppercase": "FALSE", + "first uppercase": "False", + } + + for name, viperValue := range tests { + t.Run(name, func(t *testing.T) { + v := viper.New() + v.Set("entity", "/path/to/file") + v.Set("hide-dependencies", viperValue) + + params, err := cmdparams.LoadHeartbeatParams(ctx, v) + require.NoError(t, err) + + assert.Equal(t, cmdparams.SanitizeParams{ + HideDependencies: []regex.Regex{regex.NewRegexpWrap(regexp.MustCompile("a^"))}, + }, params.Sanitize) + }) + } +} + +func TestLoadHeartbeatParams_SanitizeParams_HideDependencies_List(t *testing.T) { + ctx := context.Background() + + tests := map[string]struct { + ViperValue string + Expected []regex.Regex + }{ + "regex": { + ViperValue: "fix.*", + Expected: []regex.Regex{ + regex.NewRegexpWrap(regexp.MustCompile("(?i)fix.*")), + }, + }, + "regex list": { + ViperValue: ".*secret.*\nfix.*", + Expected: []regex.Regex{ + regex.NewRegexpWrap(regexp.MustCompile("(?i).*secret.*")), + regex.NewRegexpWrap(regexp.MustCompile("(?i)fix.*")), + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + v := viper.New() + v.Set("entity", "/path/to/file") + v.Set("hide-dependencies", test.ViperValue) + + params, err := cmdparams.LoadHeartbeatParams(ctx, v) + require.NoError(t, err) + + assert.Equal(t, cmdparams.SanitizeParams{ + HideDependencies: test.Expected, + }, params.Sanitize) + }) + } +} + +func TestLoadHeartbeatParams_SanitizeParams_HideDependencies_FlagTakesPrecedence(t *testing.T) { + v := viper.New() + v.Set("entity", "/path/to/file") + v.Set("hide-dependencies", true) + v.Set("settings.hide_dependencies", "ignored") + + params, err := cmdparams.LoadHeartbeatParams(context.Background(), v) + require.NoError(t, err) + + assert.Equal(t, cmdparams.SanitizeParams{ + HideDependencies: []regex.Regex{regex.NewRegexpWrap(regexp.MustCompile(".*"))}, + }, params.Sanitize) +} + +func TestLoadHeartbeatParams_SanitizeParams_HideDependencies_FromConfig(t *testing.T) { + v := viper.New() + v.Set("entity", "/path/to/file") + v.Set("settings.hide_dependencies", true) + + params, err := cmdparams.LoadHeartbeatParams(context.Background(), v) + require.NoError(t, err) + + assert.Equal(t, cmdparams.SanitizeParams{ + HideDependencies: []regex.Regex{regex.NewRegexpWrap(regexp.MustCompile(".*"))}, + }, params.Sanitize) +} + +func TestLoadHeartbeatParams_SanitizeParams_HideDependencies_InvalidRegex(t *testing.T) { + logFile, err := os.CreateTemp(t.TempDir(), "") + require.NoError(t, err) + + defer logFile.Close() + + ctx := context.Background() + + v := viper.New() + v.Set("entity", "/path/to/file") + v.Set("hide-dependencies", ".*secret.*\n[0-9+") + v.Set("log-file", logFile.Name()) + + logger, err := cmd.SetupLogging(ctx, v) + require.NoError(t, err) + + defer logger.Flush() + + ctx = log.ToContext(ctx, logger) + + _, err = cmdparams.LoadHeartbeatParams(ctx, v) + require.NoError(t, err) + + output, err := io.ReadAll(logFile) + require.NoError(t, err) + + assert.Contains(t, string(output), "failed to compile regex pattern \\\"(?i)[0-9+\\\", it will be ignored") +} + func TestLoadHeartbeatParams_SanitizeParams_HideProjectNames_True(t *testing.T) { ctx := context.Background() @@ -2689,7 +2846,7 @@ func TestHeartbeat_String(t *testing.T) { " project file: false), project params: (alternate: '', branch alternate: '', map patterns:"+ " '[]', override: '', git submodules disabled: '[]', git submodule project map: '[]'), sanitize"+ " params: (hide branch names: '[]', hide project folder: false, hide file names: '[]',"+ - " hide project names: '[]', project path override: '')", + " hide project names: '[]', hide dependencies: '[]', project path override: '')", heartbeat.String(), ) } @@ -2753,6 +2910,7 @@ func TestLoadHeartbeatParams_ProjectFromGitRemote(t *testing.T) { func TestSanitizeParams_String(t *testing.T) { sanitizeparams := cmdparams.SanitizeParams{ HideBranchNames: []regex.Regex{regex.NewRegexpWrap(regexp.MustCompile("^/hide"))}, + HideDependencies: []regex.Regex{regex.NewRegexpWrap(regexp.MustCompile("^/hide"))}, HideProjectFolder: true, HideFileNames: []regex.Regex{regex.NewRegexpWrap(regexp.MustCompile("^/hide"))}, HideProjectNames: []regex.Regex{regex.NewRegexpWrap(regexp.MustCompile("^/hide"))}, @@ -2762,7 +2920,7 @@ func TestSanitizeParams_String(t *testing.T) { assert.Equal( t, "hide branch names: '[^/hide]', hide project folder: true, hide file names: '[^/hide]',"+ - " hide project names: '[^/hide]', project path override: 'path/to/project'", + " hide project names: '[^/hide]', hide dependencies: '[^/hide]', project path override: 'path/to/project'", sanitizeparams.String(), ) } diff --git a/pkg/deps/deps.go b/pkg/deps/deps.go index 097b2d1c..f218248f 100644 --- a/pkg/deps/deps.go +++ b/pkg/deps/deps.go @@ -52,7 +52,12 @@ func WithDetection(c Config) heartbeat.HandleOption { continue } - if heartbeat.ShouldSanitize(ctx, h.Entity, c.FilePatterns) { + if heartbeat.ShouldSanitize(ctx, heartbeat.SanitizeCheck{ + Entity: h.Entity, + ProjectPath: h.ProjectPath, + ProjectPathOverride: h.ProjectPathOverride, + Patterns: c.FilePatterns, + }) { continue } diff --git a/pkg/heartbeat/sanitize.go b/pkg/heartbeat/sanitize.go index 5ff7c342..2de01802 100644 --- a/pkg/heartbeat/sanitize.go +++ b/pkg/heartbeat/sanitize.go @@ -11,14 +11,16 @@ import ( // SanitizeConfig defines how a heartbeat should be sanitized. type SanitizeConfig struct { - // BranchPatterns will be matched against the branch and if matching, will obfuscate it. + // BranchPatterns will be matched against the entity file path and if matching, will obfuscate it. BranchPatterns []regex.Regex + // DependencyPatterns will be matched against the entity file path and if matching, will omit all dependencies. + DependencyPatterns []regex.Regex // FilePatterns will be matched against a file entity's name and if matching will obfuscate // the file name and common heartbeat meta data (cursor position, dependencies, line number and lines). FilePatterns []regex.Regex // HideProjectFolder determines if project folder should be obfuscated. HideProjectFolder bool - // ProjectPatterns will be matched against the project name and if matching will obfuscate + // ProjectPatterns will be matched against the entity file path and if matching will obfuscate // common heartbeat meta data (cursor position, dependencies, line number and lines). ProjectPatterns []regex.Regex } @@ -47,8 +49,15 @@ func Sanitize(ctx context.Context, h Heartbeat, config SanitizeConfig) Heartbeat h.Dependencies = nil } - switch { - case ShouldSanitize(ctx, h.Entity, config.FilePatterns): + check := SanitizeCheck{ + Entity: h.Entity, + ProjectPath: h.ProjectPath, + ProjectPathOverride: h.ProjectPathOverride, + } + + // file patterns + check.Patterns = config.FilePatterns + if ShouldSanitize(ctx, check) { if h.EntityType == FileType { h.Entity = "HIDDEN" + filepath.Ext(h.Entity) } else { @@ -56,21 +65,33 @@ func Sanitize(ctx context.Context, h Heartbeat, config SanitizeConfig) Heartbeat } h = sanitizeMetaData(h) + } - if h.Branch != nil && (len(config.BranchPatterns) == 0 || ShouldSanitize(ctx, *h.Branch, config.BranchPatterns)) { - h.Branch = nil + // project patterns + if h.Project != nil { + check.Patterns = config.ProjectPatterns + if ShouldSanitize(ctx, check) { + h = sanitizeMetaData(h) } - case h.Project != nil && ShouldSanitize(ctx, *h.Project, config.ProjectPatterns): - h = sanitizeMetaData(h) - if h.Branch != nil && (len(config.BranchPatterns) == 0 || ShouldSanitize(ctx, *h.Branch, config.BranchPatterns)) { + } + + // branch patterns + if h.Branch != nil { + check.Patterns = config.BranchPatterns + if ShouldSanitize(ctx, check) { h.Branch = nil } - case h.Branch != nil && ShouldSanitize(ctx, *h.Branch, config.BranchPatterns): - h.Branch = nil } - h = hideProjectFolder(h, config.HideProjectFolder) + // dependency patterns + if h.Dependencies != nil { + check.Patterns = config.DependencyPatterns + if ShouldSanitize(ctx, check) { + h.Dependencies = nil + } + } + h = hideProjectFolder(h, config.HideProjectFolder) h = hideCredentials(h) return h @@ -130,10 +151,9 @@ func hideCredentials(h Heartbeat) Heartbeat { return h } -// sanitizeMetaData sanitizes metadata (cursor position, dependencies, line number and lines). +// sanitizeMetaData sanitizes metadata (cursor position, line number, lines and project root count). func sanitizeMetaData(h Heartbeat) Heartbeat { h.CursorPosition = nil - h.Dependencies = nil h.LineNumber = nil h.Lines = nil h.ProjectRootCount = nil @@ -141,12 +161,28 @@ func sanitizeMetaData(h Heartbeat) Heartbeat { return h } -// ShouldSanitize checks a subject (entity, project, branch) of a heartbeat and -// checks it against the passed in regex patterns to determine, if this heartbeat +// SanitizeCheck defines a configuration for checking if a heartbeat should be sanitized. +type SanitizeCheck struct { + Entity string + Patterns []regex.Regex + ProjectPath string + ProjectPathOverride string +} + +// ShouldSanitize checks the entity filepath or project path of a heartbeat +// against the passed in regex patterns to determine, if this heartbeat // should be sanitized. -func ShouldSanitize(ctx context.Context, subject string, patterns []regex.Regex) bool { - for _, p := range patterns { - if p.MatchString(ctx, subject) { +func ShouldSanitize(ctx context.Context, check SanitizeCheck) bool { + for _, p := range check.Patterns { + if p.MatchString(ctx, check.Entity) { + return true + } + + if p.MatchString(ctx, check.ProjectPath) { + return true + } + + if p.MatchString(ctx, check.ProjectPathOverride) { return true } } diff --git a/pkg/heartbeat/sanitize_test.go b/pkg/heartbeat/sanitize_test.go index 28e17057..f285c73c 100644 --- a/pkg/heartbeat/sanitize_test.go +++ b/pkg/heartbeat/sanitize_test.go @@ -20,14 +20,16 @@ func TestWithSanitization_ObfuscateFile(t *testing.T) { handle := opt(func(_ context.Context, hh []heartbeat.Heartbeat) ([]heartbeat.Result, error) { assert.Equal(t, []heartbeat.Heartbeat{ { - Category: heartbeat.CodingCategory, - Entity: "HIDDEN.go", - EntityType: heartbeat.FileType, - IsWrite: heartbeat.PointerTo(true), - Language: heartbeat.PointerTo("Go"), - Project: heartbeat.PointerTo("wakatime"), - Time: 1585598060, - UserAgent: "wakatime/13.0.7", + Branch: heartbeat.PointerTo("heartbeat"), + Category: heartbeat.CodingCategory, + Dependencies: []string{"dep1", "dep2"}, + Entity: "HIDDEN.go", + EntityType: heartbeat.FileType, + IsWrite: heartbeat.PointerTo(true), + Language: heartbeat.PointerTo("Go"), + Project: heartbeat.PointerTo("wakatime"), + Time: 1585598060, + UserAgent: "wakatime/13.0.7", }, }, hh) @@ -72,14 +74,16 @@ func TestSanitize_Obfuscate(t *testing.T) { UserAgent: "wakatime/13.0.7", }, Expected: heartbeat.Heartbeat{ - Category: heartbeat.CodingCategory, - Entity: "HIDDEN.go", - EntityType: heartbeat.FileType, - IsWrite: heartbeat.PointerTo(true), - Language: heartbeat.PointerTo("Go"), - Project: heartbeat.PointerTo("wakatime"), - Time: 1585598060, - UserAgent: "wakatime/13.0.7", + Branch: heartbeat.PointerTo("heartbeat"), + Category: heartbeat.CodingCategory, + Dependencies: []string{"dep1", "dep2"}, + Entity: "HIDDEN.go", + EntityType: heartbeat.FileType, + IsWrite: heartbeat.PointerTo(true), + Language: heartbeat.PointerTo("Go"), + Project: heartbeat.PointerTo("wakatime"), + Time: 1585598060, + UserAgent: "wakatime/13.0.7", }, }, "app": { @@ -134,21 +138,23 @@ func TestSanitize_ObfuscateFile_SkipBranchIfNotMatching(t *testing.T) { }) assert.Equal(t, heartbeat.Heartbeat{ - Branch: heartbeat.PointerTo("heartbeat"), - Category: heartbeat.CodingCategory, - Entity: "HIDDEN.go", - EntityType: heartbeat.FileType, - IsWrite: heartbeat.PointerTo(true), - Language: heartbeat.PointerTo("Go"), - Project: heartbeat.PointerTo("wakatime"), - Time: 1585598060, - UserAgent: "wakatime/13.0.7", + Branch: heartbeat.PointerTo("heartbeat"), + Dependencies: []string{"dep1", "dep2"}, + Category: heartbeat.CodingCategory, + Entity: "HIDDEN.go", + EntityType: heartbeat.FileType, + IsWrite: heartbeat.PointerTo(true), + Language: heartbeat.PointerTo("Go"), + Project: heartbeat.PointerTo("wakatime"), + Time: 1585598060, + UserAgent: "wakatime/13.0.7", }, r) } func TestSanitize_ObfuscateFile_NilFields(t *testing.T) { h := testHeartbeat() h.Branch = nil + h.Dependencies = nil r := heartbeat.Sanitize(context.Background(), h, heartbeat.SanitizeConfig{ FilePatterns: []regex.Regex{regex.NewRegexpWrap(regexp.MustCompile(".*"))}, @@ -173,14 +179,16 @@ func TestSanitize_ObfuscateProject(t *testing.T) { }) assert.Equal(t, heartbeat.Heartbeat{ - Category: heartbeat.CodingCategory, - Entity: "/tmp/main.go", - EntityType: heartbeat.FileType, - IsWrite: heartbeat.PointerTo(true), - Language: heartbeat.PointerTo("Go"), - Project: heartbeat.PointerTo("wakatime"), - Time: 1585598060, - UserAgent: "wakatime/13.0.7", + Branch: heartbeat.PointerTo("heartbeat"), + Category: heartbeat.CodingCategory, + Dependencies: []string{"dep1", "dep2"}, + Entity: "/tmp/main.go", + EntityType: heartbeat.FileType, + IsWrite: heartbeat.PointerTo(true), + Language: heartbeat.PointerTo("Go"), + Project: heartbeat.PointerTo("wakatime"), + Time: 1585598060, + UserAgent: "wakatime/13.0.7", }, r) } @@ -191,21 +199,23 @@ func TestSanitize_ObfuscateProject_SkipBranchIfNotMatching(t *testing.T) { }) assert.Equal(t, heartbeat.Heartbeat{ - Branch: heartbeat.PointerTo("heartbeat"), - Category: heartbeat.CodingCategory, - Entity: "/tmp/main.go", - EntityType: heartbeat.FileType, - IsWrite: heartbeat.PointerTo(true), - Language: heartbeat.PointerTo("Go"), - Project: heartbeat.PointerTo("wakatime"), - Time: 1585598060, - UserAgent: "wakatime/13.0.7", + Branch: heartbeat.PointerTo("heartbeat"), + Category: heartbeat.CodingCategory, + Dependencies: []string{"dep1", "dep2"}, + Entity: "/tmp/main.go", + EntityType: heartbeat.FileType, + IsWrite: heartbeat.PointerTo(true), + Language: heartbeat.PointerTo("Go"), + Project: heartbeat.PointerTo("wakatime"), + Time: 1585598060, + UserAgent: "wakatime/13.0.7", }, r) } func TestSanitize_ObfuscateProject_NilFields(t *testing.T) { h := testHeartbeat() h.Branch = nil + h.Dependencies = nil r := heartbeat.Sanitize(context.Background(), h, heartbeat.SanitizeConfig{ ProjectPatterns: []regex.Regex{regex.NewRegexpWrap(regexp.MustCompile(".*"))}, @@ -269,6 +279,27 @@ func TestSanitize_ObfuscateBranch_NilFields(t *testing.T) { }, r) } +func TestSanitize_ObfuscateDependency(t *testing.T) { + r := heartbeat.Sanitize(context.Background(), testHeartbeat(), heartbeat.SanitizeConfig{ + DependencyPatterns: []regex.Regex{regex.NewRegexpWrap(regexp.MustCompile(".*"))}, + }) + + assert.Equal(t, heartbeat.Heartbeat{ + Branch: heartbeat.PointerTo("heartbeat"), + Category: heartbeat.CodingCategory, + CursorPosition: heartbeat.PointerTo(12), + Entity: "/tmp/main.go", + EntityType: heartbeat.FileType, + IsWrite: heartbeat.PointerTo(true), + Language: heartbeat.PointerTo("Go"), + LineNumber: heartbeat.PointerTo(42), + Lines: heartbeat.PointerTo(100), + Project: heartbeat.PointerTo("wakatime"), + Time: 1585598060, + UserAgent: "wakatime/13.0.7", + }, r) +} + func TestSanitize_EmptyConfigDoNothing(t *testing.T) { r := heartbeat.Sanitize(context.Background(), testHeartbeat(), heartbeat.SanitizeConfig{}) @@ -425,7 +456,10 @@ func TestShouldSanitize(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - shouldSanitize := heartbeat.ShouldSanitize(ctx, test.Subject, test.Regex) + shouldSanitize := heartbeat.ShouldSanitize(ctx, heartbeat.SanitizeCheck{ + Entity: test.Subject, + Patterns: test.Regex, + }) assert.Equal(t, test.Expected, shouldSanitize) }) diff --git a/pkg/project/project.go b/pkg/project/project.go index 621a757c..8fff63f8 100644 --- a/pkg/project/project.go +++ b/pkg/project/project.go @@ -201,8 +201,12 @@ func WithDetection(config Config) heartbeat.HandleOption { } // finally, obfuscate project name if necessary - if heartbeat.ShouldSanitize(ctx, result.Folder, config.HideProjectNames) && - result.Project != "" && detector != FileDetector { + if heartbeat.ShouldSanitize(ctx, heartbeat.SanitizeCheck{ + Entity: h.Entity, + ProjectPath: result.Folder, + Patterns: config.HideProjectNames, + ProjectPathOverride: h.ProjectPathOverride, + }) && result.Project != "" && detector != FileDetector { result.Project = obfuscateProjectName(ctx, result.Folder) }