package delivery_test import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sneak.berlin/go/webhooker/internal/database" "sneak.berlin/go/webhooker/internal/delivery" ) const ( // slackSecretPath is the credential-bearing part of a // Slack incoming webhook URL: everything after the host. slackSecretPath = "/services/T00000000/B00000000/" + "XXXXXXXXXXXXXXXXXXXXXXXX" slackWebhookURL = "https://hooks.slack.com" + slackSecretPath viewExampleOrigin = "https://example.com" viewExampleHook = viewExampleOrigin + "/hook" viewMaskedOrigin = viewExampleOrigin + "/..." viewUnavailable = "(unavailable)" viewExpiryNever = "never" ) func TestMaskedWebhookURL(t *testing.T) { t.Parallel() tests := map[string]struct { url string want string }{ "slack webhook": { url: slackWebhookURL, want: "https://hooks.slack.com/...", }, "query string dropped": { url: viewExampleOrigin + "/a?token=secret", want: viewExampleOrigin + "/...", }, // Fabricated userinfo in a test URL, not a real // credential. //nolint:gosec // G101 "userinfo dropped": { url: "https://user:pw@example.com/a/b", want: viewExampleOrigin + "/...", }, "no path": { url: viewExampleOrigin, want: viewExampleOrigin, }, "root path": { url: viewExampleOrigin + "/", want: viewExampleOrigin, }, "not a url": { url: "definitely not a url", want: viewUnavailable, }, "empty": { url: "", want: viewUnavailable, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { t.Parallel() cfg := &delivery.SlackTargetConfig{ WebhookURL: tc.url, } assert.Equal( t, tc.want, cfg.MaskedWebhookURL(), ) }) } } // TestMaskedWebhookURL_NeverLeaksPath is the direct // expression of the rule: whatever the input, the masked // value never contains a path segment of it. func TestMaskedWebhookURL_NeverLeaksPath(t *testing.T) { t.Parallel() cfg := &delivery.SlackTargetConfig{ WebhookURL: slackWebhookURL, } masked := cfg.MaskedWebhookURL() assert.NotContains(t, masked, "T00000000") assert.NotContains(t, masked, "B00000000") assert.NotContains( t, masked, "XXXXXXXXXXXXXXXXXXXXXXXX", ) assert.NotContains(t, masked, slackSecretPath) } // fieldMap turns a view's config fields into a lookup so // assertions read by label. func fieldMap(fields []delivery.ConfigField) map[string]string { out := make(map[string]string, len(fields)) for _, f := range fields { out[f.Label] = f.Value } return out } // viewFor projects a single target and returns its view. func viewFor( t *testing.T, target database.Target, ) delivery.TargetView { t.Helper() views := delivery.NewTargetViews( []database.Target{target}, ) require.Len(t, views, 1) return views[0] } func TestNewTargetViews_Slack(t *testing.T) { t.Parallel() view := viewFor(t, database.Target{ Name: "slack-target", Type: database.TargetTypeSlack, Active: true, Config: `{"webhookUrl":"` + slackWebhookURL + `"}`, }) assert.Equal(t, "slack-target", view.Name) assert.Equal( t, map[string]string{ "Webhook URL": "https://hooks.slack.com/...", }, fieldMap(view.Config), ) } func TestNewTargetViews_HTTP(t *testing.T) { t.Parallel() view := viewFor(t, database.Target{ Type: database.TargetTypeHTTP, Config: `{"url":"` + viewExampleHook + `",` + `"timeout":30,` + `"headers":{"Authorization":"Bearer sekrit"}}`, MaxRetries: 5, MaxQueueSize: 100, }) fields := fieldMap(view.Config) assert.Equal( t, map[string]string{ "Destination URL": viewMaskedOrigin, "Timeout": "30s", "Headers": "1 configured", "Max Retries": "5", "Max Queue Size": "100", }, fields, ) // Header values can be credentials and are never shown. for _, v := range fields { assert.NotContains(t, v, "sekrit") } } func TestNewTargetViews_HTTPFireAndForget(t *testing.T) { t.Parallel() view := viewFor(t, database.Target{ Type: database.TargetTypeHTTP, Config: `{"url":"` + viewExampleHook + `"}`, }) assert.Equal( t, map[string]string{ "Destination URL": viewMaskedOrigin, "Max Retries": "0 (fire-and-forget)", }, fieldMap(view.Config), ) } // TestNewTargetViews_HTTPMasksDestinationURL proves the rule // holds for the http target too: an http destination is // routinely an incoming-webhook endpoint whose path segments // are the credential, so none of them is shown. func TestNewTargetViews_HTTPMasksDestinationURL(t *testing.T) { t.Parallel() view := viewFor(t, database.Target{ Type: database.TargetTypeHTTP, Config: `{"url":"` + slackWebhookURL + `"}`, }) fields := fieldMap(view.Config) assert.Equal( t, "https://hooks.slack.com/...", fields["Destination URL"], ) for _, v := range fields { assert.NotContains(t, v, slackSecretPath) assert.NotContains(t, v, "T00000000") assert.NotContains(t, v, "B00000000") assert.NotContains(t, v, "XXXXXXXXXXXXXXXXXXXXXXXX") } } func TestNewTargetViews_Database(t *testing.T) { t.Parallel() tests := map[string]struct { config string want string }{ "empty config": {config: "", want: viewExpiryNever}, "empty expiry": {config: `{}`, want: viewExpiryNever}, "explicit": { config: `{"expiry":"720h"}`, want: "720h", }, "never literal": { config: `{"expiry":"` + viewExpiryNever + `"}`, want: viewExpiryNever, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { t.Parallel() view := viewFor(t, database.Target{ Type: database.TargetTypeDatabase, Config: tc.config, }) assert.Equal( t, map[string]string{"Archive Expiry": tc.want}, fieldMap(view.Config), ) }) } } func TestNewTargetViews_Log(t *testing.T) { t.Parallel() view := viewFor(t, database.Target{ Type: database.TargetTypeLog, Config: "", }) assert.Empty(t, view.Config) } // TestNewTargetViews_Unpresentable proves that no config the // view cannot present falls back to the stored blob. func TestNewTargetViews_Unpresentable(t *testing.T) { t.Parallel() const blob = `{"webhookUrl":"https://hooks.slack.com` + slackSecretPath + `"` tests := map[string]database.Target{ "unknown target type": { Type: database.TargetType("carrier-pigeon"), Config: blob, }, "unparseable json": { Type: database.TargetTypeSlack, Config: blob, }, "empty slack config": { Type: database.TargetTypeSlack, }, "slack config without url": { Type: database.TargetTypeSlack, Config: `{}`, }, "unparseable http json": { Type: database.TargetTypeHTTP, Config: `{"url":`, }, "unparseable archive json": { Type: database.TargetTypeDatabase, Config: `{"expiry":`, }, "invalid archive expiry": { Type: database.TargetTypeDatabase, Config: `{"expiry":"a fortnight"}`, }, } for name, target := range tests { t.Run(name, func(t *testing.T) { t.Parallel() view := viewFor(t, target) assert.Equal( t, map[string]string{ "Configuration": viewUnavailable, }, fieldMap(view.Config), ) }) } }