diff --git a/CHANGELOG.md b/CHANGELOG.md index bf2849c..306370a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v0.3.6] +- Exported helper functions related to InternalWebhook struct. [#94](https://github.com/xmidt-org/ancla/pull/94) + ## [v0.3.5] - Changed errorEncoder to log errors. [#90](https://github.com/xmidt-org/ancla/pull/90) - Fixed webhook request decoder panic; added default validator when none given. [#92](https://github.com/xmidt-org/ancla/pull/92) diff --git a/internalWebhook.go b/internalWebhook.go new file mode 100644 index 0000000..209ae0b --- /dev/null +++ b/internalWebhook.go @@ -0,0 +1,90 @@ +/** + * Copyright 2022 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ancla + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "math" + "time" + + "github.com/xmidt-org/argus/model" +) + +type InternalWebhook struct { + PartnerIDs []string + Webhook Webhook +} + +func InternalWebhookToItem(now func() time.Time, iw InternalWebhook) (model.Item, error) { + encodedWebhook, err := json.Marshal(iw) + if err != nil { + return model.Item{}, err + } + var data map[string]interface{} + + err = json.Unmarshal(encodedWebhook, &data) + if err != nil { + return model.Item{}, err + } + + SecondsToExpiry := iw.Webhook.Until.Sub(now()).Seconds() + TTLSeconds := int64(math.Max(0, SecondsToExpiry)) + + checksum := fmt.Sprintf("%x", sha256.Sum256([]byte(iw.Webhook.Config.URL))) + + return model.Item{ + Data: data, + ID: checksum, + TTL: &TTLSeconds, + }, nil +} + +func ItemToInternalWebhook(i model.Item) (InternalWebhook, error) { + encodedWebhook, err := json.Marshal(i.Data) + if err != nil { + return InternalWebhook{}, err + } + var iw InternalWebhook + err = json.Unmarshal(encodedWebhook, &iw) + if err != nil { + return InternalWebhook{}, err + } + return iw, nil +} + +func ItemsToInternalWebhooks(items []model.Item) ([]InternalWebhook, error) { + iws := []InternalWebhook{} + for _, item := range items { + iw, err := ItemToInternalWebhook(item) + if err != nil { + return nil, err + } + iws = append(iws, iw) + } + return iws, nil +} + +func InternalWebhooksToWebhooks(iws []InternalWebhook) []Webhook { + w := make([]Webhook, 0, len(iws)) + for _, iw := range iws { + w = append(w, iw.Webhook) + } + return w +} diff --git a/internalWebhook_test.go b/internalWebhook_test.go new file mode 100644 index 0000000..70010c4 --- /dev/null +++ b/internalWebhook_test.go @@ -0,0 +1,200 @@ +/** + * Copyright 2022 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ancla + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/xmidt-org/argus/model" +) + +func TestItemToInternalWebhook(t *testing.T) { + items := getTestItems() + iws := getTestInternalWebhooks() + tcs := []struct { + Description string + InputItem model.Item + ExpectedInternalWebhook InternalWebhook + ShouldErr bool + }{ + { + Description: "Err Marshaling", + InputItem: model.Item{ + Data: map[string]interface{}{ + "cannotUnmarshal": make(chan int), + }, + }, + ShouldErr: true, + }, + { + Description: "Success", + InputItem: items[0], + ExpectedInternalWebhook: iws[0], + }, + } + + for _, tc := range tcs { + t.Run(tc.Description, func(t *testing.T) { + assert := assert.New(t) + w, err := ItemToInternalWebhook(tc.InputItem) + if tc.ShouldErr { + assert.Error(err) + } + assert.Equal(tc.ExpectedInternalWebhook, w) + }) + } +} + +func TestInternalWebhookToItem(t *testing.T) { + refTime := getRefTime() + fixedNow := func() time.Time { + return refTime + } + items := getTestItems() + iws := getTestInternalWebhooks() + tcs := []struct { + Description string + InputInternalWebhook InternalWebhook + ExpectedItem model.Item + ShouldErr bool + }{ + { + Description: "Expired item", + InputInternalWebhook: getExpiredInternalWebhook(), + ExpectedItem: getExpiredItem(), + }, + { + Description: "Happy path", + InputInternalWebhook: iws[0], + ExpectedItem: items[0], + }, + } + + for _, tc := range tcs { + t.Run(tc.Description, func(t *testing.T) { + assert := assert.New(t) + item, err := InternalWebhookToItem(fixedNow, tc.InputInternalWebhook) + if tc.ShouldErr { + assert.Error(err) + } + assert.Equal(tc.ExpectedItem, item) + }) + } +} + +func getExpiredItem() model.Item { + var expiresInSecs int64 = 0 + return model.Item{ + ID: "b3bbc3467366959e0aba3c33588a08c599f68a740fabf4aa348463d3dc7dcfe8", + Data: map[string]interface{}{ + "Webhook": map[string]interface{}{ + "registered_from_address": "http://original-requester.example.net", + "config": map[string]interface{}{ + "url": "http://deliver-here-0.example.net", + "content_type": "application/json", + "secret": "superSecretXYZ", + }, + "events": []interface{}{"online"}, + "matcher": map[string]interface{}{ + "device_id": []interface{}{"mac:aabbccddee.*"}, + }, + "failure_url": "http://contact-here-when-fails.example.net", + "duration": float64(time.Second.Nanoseconds()), + "until": "1970-01-01T00:00:01Z", + }, + "PartnerIDs": []interface{}{}, + }, + TTL: &expiresInSecs, + } +} + +func getExpiredInternalWebhook() InternalWebhook { + return InternalWebhook{ + Webhook: Webhook{ + Address: "http://original-requester.example.net", + Config: DeliveryConfig{ + URL: "http://deliver-here-0.example.net", + ContentType: "application/json", + Secret: "superSecretXYZ", + }, + Events: []string{"online"}, + Matcher: struct { + DeviceID []string `json:"device_id"` + }{ + DeviceID: []string{"mac:aabbccddee.*"}, + }, + FailureURL: "http://contact-here-when-fails.example.net", + Duration: time.Second, + Until: time.Unix(1, 0).UTC(), + }, + PartnerIDs: []string{}, + } +} + +func getTestInternalWebhooks() []InternalWebhook { + refTime := getRefTime() + return []InternalWebhook{ + { + Webhook: Webhook{ + Address: "http://original-requester.example.net", + Config: DeliveryConfig{ + URL: "http://deliver-here-0.example.net", + ContentType: "application/json", + Secret: "superSecretXYZ", + }, + Events: []string{"online"}, + Matcher: MetadataMatcherConfig{ + DeviceID: []string{"mac:aabbccddee.*"}, + }, + FailureURL: "http://contact-here-when-fails.example.net", + Duration: 10 * time.Second, + Until: refTime.Add(10 * time.Second), + }, + PartnerIDs: []string{"comcast"}, + }, + { + Webhook: Webhook{ + Address: "http://original-requester.example.net", + Config: DeliveryConfig{ + ContentType: "application/json", + URL: "http://deliver-here-1.example.net", + Secret: "doNotShare:e=mc^2", + }, + Events: []string{"online"}, + Matcher: MetadataMatcherConfig{ + DeviceID: []string{"mac:aabbccddee.*"}, + }, + + FailureURL: "http://contact-here-when-fails.example.net", + Duration: 20 * time.Second, + Until: refTime.Add(20 * time.Second), + }, + PartnerIDs: []string{}, + }, + } +} + +func getRefTime() time.Time { + refTime, err := time.Parse(time.RFC3339, "2021-01-02T15:04:00Z") + if err != nil { + panic(err) + } + return refTime +} diff --git a/service.go b/service.go index 0d055e5..064c3da 100644 --- a/service.go +++ b/service.go @@ -1,5 +1,5 @@ /** - * Copyright 2021 Comcast Cable Communications Management, LLC + * Copyright 2022 Comcast Cable Communications Management, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,17 +19,13 @@ package ancla import ( "context" - "crypto/sha256" - "encoding/json" "errors" "fmt" - "math" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/xmidt-org/argus/chrysom" - "github.com/xmidt-org/argus/model" "github.com/xmidt-org/webpa-common/v2/logging" "github.com/xmidt-org/webpa-common/v2/xmetrics" ) @@ -101,13 +97,8 @@ type service struct { now func() time.Time } -type InternalWebhook struct { - PartnerIDs []string - Webhook Webhook -} - func (s *service) Add(ctx context.Context, owner string, iw InternalWebhook) error { - item, err := internalWebhookToItem(s.now, iw) + item, err := InternalWebhookToItem(s.now, iw) if err != nil { return fmt.Errorf(errFmt, errFailedWebhookConversion, err) } @@ -133,7 +124,7 @@ func (s *service) GetAll(ctx context.Context) ([]InternalWebhook, error) { iws := make([]InternalWebhook, len(items)) for i, item := range items { - webhook, err := itemToInternalWebhook(item) + webhook, err := ItemToInternalWebhook(item) if err != nil { return nil, fmt.Errorf(errFmt, errFailedItemConversion, err) } @@ -143,43 +134,6 @@ func (s *service) GetAll(ctx context.Context) ([]InternalWebhook, error) { return iws, nil } -func internalWebhookToItem(now func() time.Time, iw InternalWebhook) (model.Item, error) { - encodedWebhook, err := json.Marshal(iw) - if err != nil { - return model.Item{}, err - } - var data map[string]interface{} - - err = json.Unmarshal(encodedWebhook, &data) - if err != nil { - return model.Item{}, err - } - - SecondsToExpiry := iw.Webhook.Until.Sub(now()).Seconds() - TTLSeconds := int64(math.Max(0, SecondsToExpiry)) - - checksum := fmt.Sprintf("%x", sha256.Sum256([]byte(iw.Webhook.Config.URL))) - - return model.Item{ - Data: data, - ID: checksum, - TTL: &TTLSeconds, - }, nil -} - -func itemToInternalWebhook(i model.Item) (InternalWebhook, error) { - encodedWebhook, err := json.Marshal(i.Data) - if err != nil { - return InternalWebhook{}, err - } - var iw InternalWebhook - err = json.Unmarshal(encodedWebhook, &iw) - if err != nil { - return InternalWebhook{}, err - } - return iw, nil -} - func validateConfig(cfg *Config) { if cfg.Logger == nil { cfg.Logger = log.NewNopLogger() @@ -234,7 +188,7 @@ func prepArgusConfig(cfg *Config, watches ...Watch) error { func createArgusListener(logger log.Logger, watches ...Watch) chrysom.Listener { return chrysom.ListenerFunc(func(items chrysom.Items) { - iws, err := itemsToInternalWebhooks(items) + iws, err := ItemsToInternalWebhooks(items) if err != nil { level.Error(logger).Log(logging.MessageKey(), "Failed to convert items to webhooks", "err", err) return @@ -244,23 +198,3 @@ func createArgusListener(logger log.Logger, watches ...Watch) chrysom.Listener { } }) } - -func itemsToInternalWebhooks(items []model.Item) ([]InternalWebhook, error) { - iws := []InternalWebhook{} - for _, item := range items { - iw, err := itemToInternalWebhook(item) - if err != nil { - return nil, err - } - iws = append(iws, iw) - } - return iws, nil -} - -func internalWebhooksToWebhooks(iws []InternalWebhook) []Webhook { - w := make([]Webhook, 0, len(iws)) - for _, iw := range iws { - w = append(w, iw.Webhook) - } - return w -} diff --git a/service_test.go b/service_test.go index 1ddafd3..2197af6 100644 --- a/service_test.go +++ b/service_test.go @@ -1,5 +1,5 @@ /** - * Copyright 2021 Comcast Cable Communications Management, LLC + * Copyright 2022 Comcast Cable Communications Management, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,6 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/xmidt-org/argus/chrysom" - "github.com/xmidt-org/argus/model" "github.com/xmidt-org/webpa-common/v2/xmetrics" ) @@ -183,128 +182,6 @@ func TestAllInternalWebhooks(t *testing.T) { } } -func TestItemToInternalWebhook(t *testing.T) { - items := getTestItems() - iws := getTestInternalWebhooks() - tcs := []struct { - Description string - InputItem model.Item - ExpectedInternalWebhook InternalWebhook - ShouldErr bool - }{ - { - Description: "Err Marshaling", - InputItem: model.Item{ - Data: map[string]interface{}{ - "cannotUnmarshal": make(chan int), - }, - }, - ShouldErr: true, - }, - { - Description: "Success", - InputItem: items[0], - ExpectedInternalWebhook: iws[0], - }, - } - - for _, tc := range tcs { - t.Run(tc.Description, func(t *testing.T) { - assert := assert.New(t) - w, err := itemToInternalWebhook(tc.InputItem) - if tc.ShouldErr { - assert.Error(err) - } - assert.Equal(tc.ExpectedInternalWebhook, w) - }) - } -} -func TestInternalWebhookToItem(t *testing.T) { - refTime := getRefTime() - fixedNow := func() time.Time { - return refTime - } - items := getTestItems() - iws := getTestInternalWebhooks() - tcs := []struct { - Description string - InputInternalWebhook InternalWebhook - ExpectedItem model.Item - ShouldErr bool - }{ - { - Description: "Expired item", - InputInternalWebhook: getExpiredInternalWebhook(), - ExpectedItem: getExpiredItem(), - }, - { - Description: "Happy path", - InputInternalWebhook: iws[0], - ExpectedItem: items[0], - }, - } - - for _, tc := range tcs { - t.Run(tc.Description, func(t *testing.T) { - assert := assert.New(t) - item, err := internalWebhookToItem(fixedNow, tc.InputInternalWebhook) - if tc.ShouldErr { - assert.Error(err) - } - assert.Equal(tc.ExpectedItem, item) - }) - } -} - -func getExpiredItem() model.Item { - var expiresInSecs int64 = 0 - return model.Item{ - ID: "b3bbc3467366959e0aba3c33588a08c599f68a740fabf4aa348463d3dc7dcfe8", - Data: map[string]interface{}{ - "Webhook": map[string]interface{}{ - "registered_from_address": "http://original-requester.example.net", - "config": map[string]interface{}{ - "url": "http://deliver-here-0.example.net", - "content_type": "application/json", - "secret": "superSecretXYZ", - }, - "events": []interface{}{"online"}, - "matcher": map[string]interface{}{ - "device_id": []interface{}{"mac:aabbccddee.*"}, - }, - "failure_url": "http://contact-here-when-fails.example.net", - "duration": float64(time.Second.Nanoseconds()), - "until": "1970-01-01T00:00:01Z", - }, - "PartnerIDs": []interface{}{}, - }, - TTL: &expiresInSecs, - } -} - -func getExpiredInternalWebhook() InternalWebhook { - return InternalWebhook{ - Webhook: Webhook{ - Address: "http://original-requester.example.net", - Config: DeliveryConfig{ - URL: "http://deliver-here-0.example.net", - ContentType: "application/json", - Secret: "superSecretXYZ", - }, - Events: []string{"online"}, - Matcher: struct { - DeviceID []string `json:"device_id"` - }{ - DeviceID: []string{"mac:aabbccddee.*"}, - }, - FailureURL: "http://contact-here-when-fails.example.net", - Duration: time.Second, - Until: time.Unix(1, 0).UTC(), - }, - PartnerIDs: []string{}, - } -} - func getTestItems() chrysom.Items { var ( firstItemExpiresInSecs int64 = 10 @@ -358,54 +235,3 @@ func getTestItems() chrysom.Items { }, } } - -func getTestInternalWebhooks() []InternalWebhook { - refTime := getRefTime() - return []InternalWebhook{ - { - Webhook: Webhook{ - Address: "http://original-requester.example.net", - Config: DeliveryConfig{ - URL: "http://deliver-here-0.example.net", - ContentType: "application/json", - Secret: "superSecretXYZ", - }, - Events: []string{"online"}, - Matcher: MetadataMatcherConfig{ - DeviceID: []string{"mac:aabbccddee.*"}, - }, - FailureURL: "http://contact-here-when-fails.example.net", - Duration: 10 * time.Second, - Until: refTime.Add(10 * time.Second), - }, - PartnerIDs: []string{"comcast"}, - }, - { - Webhook: Webhook{ - Address: "http://original-requester.example.net", - Config: DeliveryConfig{ - ContentType: "application/json", - URL: "http://deliver-here-1.example.net", - Secret: "doNotShare:e=mc^2", - }, - Events: []string{"online"}, - Matcher: MetadataMatcherConfig{ - DeviceID: []string{"mac:aabbccddee.*"}, - }, - - FailureURL: "http://contact-here-when-fails.example.net", - Duration: 20 * time.Second, - Until: refTime.Add(20 * time.Second), - }, - PartnerIDs: []string{}, - }, - } -} - -func getRefTime() time.Time { - refTime, err := time.Parse(time.RFC3339, "2021-01-02T15:04:00Z") - if err != nil { - panic(err) - } - return refTime -} diff --git a/transport.go b/transport.go index da09f5f..309d41e 100644 --- a/transport.go +++ b/transport.go @@ -72,7 +72,7 @@ type GetLoggerFunc func(context.Context) log.Logger func encodeGetAllWebhooksResponse(ctx context.Context, rw http.ResponseWriter, response interface{}) error { iws := response.([]InternalWebhook) - webhooks := internalWebhooksToWebhooks(iws) + webhooks := InternalWebhooksToWebhooks(iws) if webhooks == nil { // prefer JSON output to be "[]" instead of "" webhooks = []Webhook{}