Skip to content

Commit

Permalink
fix(teams): use correct path in webhook URL (#188)
Browse files Browse the repository at this point in the history
* add static version meta info
* fix(teams): use correct path in webhook URL
  • Loading branch information
piksel authored Aug 10, 2021
1 parent 57b43a3 commit f16b81d
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 34 deletions.
6 changes: 4 additions & 2 deletions cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import (
"github.com/containrrr/shoutrrr/cli/cmd/generate"
"github.com/containrrr/shoutrrr/cli/cmd/send"
"github.com/containrrr/shoutrrr/cli/cmd/verify"
"github.com/containrrr/shoutrrr/internal/meta"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os"
)

var cmd = &cobra.Command{
Use: "shoutrrr",
Short: "Notification library for gophers and their furry friends",
Use: "shoutrrr",
Version: meta.Version,
Short: "Shoutrrr CLI",
}

func init() {
Expand Down
7 changes: 7 additions & 0 deletions internal/meta/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package meta

// Version of Shoutrrr
const Version = `0.5-dev`

// DocsVersion is prepended to documentation URLs and usually equals MAJOR.MINOR of Version
const DocsVersion = `dev`
12 changes: 8 additions & 4 deletions pkg/services/teams/teams.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/containrrr/shoutrrr/pkg/format"
"net/http"
"net/url"
"strings"

"github.com/containrrr/shoutrrr/pkg/format"
"github.com/containrrr/shoutrrr/pkg/services/standard"
"github.com/containrrr/shoutrrr/pkg/types"
"github.com/containrrr/shoutrrr/pkg/util"
)

// Service providing teams as a notification service
Expand All @@ -35,7 +36,7 @@ func (service *Service) Send(message string, params *types.Params) error {
func (service *Service) Initialize(configURL *url.URL, logger types.StdLogger) error {
service.Logger.SetLogger(logger)
service.config = &Config{
Host: DefaultHost,
Host: LegacyHost,
}

service.pkr = format.NewPropKeyResolver(service.config)
Expand Down Expand Up @@ -93,9 +94,12 @@ func (service *Service) doSend(config *Config, message string) error {

host := config.Host
if host == "" {
host = DefaultHost
host = LegacyHost
// Emit a warning to the log for now.
// TODO(v0.6): Remove legacy support as it should be fully deprecated now
service.Logf(`Warning: No host specified, update your Teams URL: %s`, util.DocsURL(`services/teams`))
}
postURL := buildWebhookURL(host, config.webhookParts())
postURL := buildWebhookURL(host, config.Group, config.Tenant, config.AltID, config.GroupOwner)

res, err := http.Post(postURL, "application/json", bytes.NewBuffer(payload))
if err == nil && res.StatusCode != http.StatusOK {
Expand Down
29 changes: 21 additions & 8 deletions pkg/services/teams/teams_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,21 @@ func (config *Config) setFromWebhookParts(parts [4]string) {
config.GroupOwner = parts[3]
}

func buildWebhookURL(host string, parts [4]string) string {
func buildWebhookURL(host, group, tenant, altID, groupOwner string) string {
// config.Group, config.Tenant, config.AltID, config.GroupOwner
path := Path
if host == LegacyHost {
path = LegacyPath
}
return fmt.Sprintf(
"https://%s/webhook/%s@%s/IncomingWebhook/%s/%s",
"https://%s/%s/%s@%s/%s/%s/%s",
host,
parts[0],
parts[1],
parts[2],
parts[3])
path,
group,
tenant,
ProviderName,
altID,
groupOwner)
}

func parseAndVerifyWebhookURL(webhookURL string) (parts [4]string, err error) {
Expand All @@ -142,6 +149,12 @@ func parseAndVerifyWebhookURL(webhookURL string) (parts [4]string, err error) {
const (
// Scheme is the identifying part of this service's configuration URL
Scheme = "teams"
// DefaultHost is the default host for the webhook request
DefaultHost = "outlook.office.com"
// LegacyHost is the default host for legacy webhook requests
LegacyHost = "outlook.office.com"
// LegacyPath is the initial path of the webhook URL for legacy webhook requests
LegacyPath = "webhook"
// Path is the initial path of the webhook URL for domain-scoped webhook requests
Path = "webhookb2"
// ProviderName is the name of the Teams integration provider
ProviderName = "IncomingWebhook"
)
48 changes: 32 additions & 16 deletions pkg/services/teams/teams_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
)

const (
testWebhookURL = "https://outlook.office.com/webhook/11111111-4444-4444-8444-cccccccccccc@22222222-4444-4444-8444-cccccccccccc/IncomingWebhook/33333333012222222222333333333344/44444444-4444-4444-8444-cccccccccccc"
customURL = "teams+https://publicservice.info/webhook/11111111-4444-4444-8444-cccccccccccc@22222222-4444-4444-8444-cccccccccccc/IncomingWebhook/33333333012222222222333333333344/44444444-4444-4444-8444-cccccccccccc"
testURLBase = "teams://11111111-4444-4444-8444-cccccccccccc@22222222-4444-4444-8444-cccccccccccc/33333333012222222222333333333344/44444444-4444-4444-8444-cccccccccccc"
legacyWebhookURL = "https://outlook.office.com/webhook/11111111-4444-4444-8444-cccccccccccc@22222222-4444-4444-8444-cccccccccccc/IncomingWebhook/33333333012222222222333333333344/44444444-4444-4444-8444-cccccccccccc"
scopedWebhookURL = "https://test.webhook.office.com/webhookb2/11111111-4444-4444-8444-cccccccccccc@22222222-4444-4444-8444-cccccccccccc/IncomingWebhook/33333333012222222222333333333344/44444444-4444-4444-8444-cccccccccccc"
scopedDomainHost = "test.webhook.office.com"
testURLBase = "teams://11111111-4444-4444-8444-cccccccccccc@22222222-4444-4444-8444-cccccccccccc/33333333012222222222333333333344/44444444-4444-4444-8444-cccccccccccc"
scopedURLBase = testURLBase + `?host=` + scopedDomainHost
)

var logger = log.New(GinkgoWriter, "Test", log.LstdFlags)
Expand All @@ -24,18 +26,33 @@ func TestTeams(t *testing.T) {
RunSpecs(t, "Shoutrrr Teams Suite")
}

var _ = Describe("the teams plugin", func() {
var _ = Describe("the teams service", func() {
When("creating the webhook URL", func() {
It("should match the expected output", func() {
It("should match the expected output for legacy URLs", func() {
config := Config{}
config.setFromWebhookParts([4]string{
"11111111-4444-4444-8444-cccccccccccc",
"22222222-4444-4444-8444-cccccccccccc",
"33333333012222222222333333333344",
"44444444-4444-4444-8444-cccccccccccc",
})
apiURL := buildWebhookURL(DefaultHost, config.webhookParts())
Expect(apiURL).To(Equal(testWebhookURL))
apiURL := buildWebhookURL(LegacyHost, config.Group, config.Tenant, config.AltID, config.GroupOwner)
Expect(apiURL).To(Equal(legacyWebhookURL))

parts, err := parseAndVerifyWebhookURL(apiURL)
Expect(err).ToNot(HaveOccurred())
Expect(parts).To(Equal(config.webhookParts()))
})
It("should match the expected output for custom URLs", func() {
config := Config{}
config.setFromWebhookParts([4]string{
"11111111-4444-4444-8444-cccccccccccc",
"22222222-4444-4444-8444-cccccccccccc",
"33333333012222222222333333333344",
"44444444-4444-4444-8444-cccccccccccc",
})
apiURL := buildWebhookURL(scopedDomainHost, config.Group, config.Tenant, config.AltID, config.GroupOwner)
Expect(apiURL).To(Equal(scopedWebhookURL))

parts, err := parseAndVerifyWebhookURL(apiURL)
Expect(err).ToNot(HaveOccurred())
Expand All @@ -46,18 +63,17 @@ var _ = Describe("the teams plugin", func() {
Describe("creating a config", func() {
When("parsing the configuration URL", func() {
It("should be identical after de-/serialization", func() {
testURL := testURLBase + "?color=aabbcc&host=notdefault.outlook.office.com&title=Test+title"
testURL := testURLBase + "?color=aabbcc&host=test.outlook.office.com&title=Test+title"

url, err := url.Parse(testURL)
Expect(err).NotTo(HaveOccurred(), "parsing")

config := &Config{Host: DefaultHost}
config := &Config{Host: LegacyHost}
err = config.SetURL(url)
Expect(err).NotTo(HaveOccurred(), "verifying")

outputURL := config.GetURL()
Expect(outputURL.String()).To(Equal(testURL))

})
})
})
Expand All @@ -78,19 +94,19 @@ var _ = Describe("the teams plugin", func() {
When("a valid custom URL is provided", func() {
It("should set the host field from the custom URL", func() {
service := Service{}
testURL := customURL
testURL := `teams+` + scopedWebhookURL

customURL, err := url.Parse(testURL)
Expect(err).NotTo(HaveOccurred(), "parsing")

serviceURL, err := service.GetConfigURLFromCustom(customURL)
Expect(err).NotTo(HaveOccurred(), "converting")

Expect(serviceURL.String()).To(Equal(testURLBase + "?host=publicservice.info"))
Expect(serviceURL.String()).To(Equal(scopedURLBase))
})
It("should preserve the query params in the generated service URL", func() {
service := Service{}
testURL := "teams+" + testWebhookURL + "?color=f008c1&title=TheTitle"
testURL := "teams+" + legacyWebhookURL + "?color=f008c1&title=TheTitle"

customURL, err := url.Parse(testURL)
Expect(err).NotTo(HaveOccurred(), "parsing")
Expand All @@ -113,11 +129,11 @@ var _ = Describe("the teams plugin", func() {
httpmock.DeactivateAndReset()
})
It("should not report an error if the server accepts the payload", func() {
serviceURL, _ := url.Parse(testURLBase)
serviceURL, _ := url.Parse(scopedURLBase)
err = service.Initialize(serviceURL, logger)
Expect(err).NotTo(HaveOccurred())

httpmock.RegisterResponder("POST", testWebhookURL, httpmock.NewStringResponder(200, ""))
httpmock.RegisterResponder("POST", scopedWebhookURL, httpmock.NewStringResponder(200, ""))

err = service.Send("Message", nil)
Expect(err).NotTo(HaveOccurred())
Expand All @@ -127,7 +143,7 @@ var _ = Describe("the teams plugin", func() {
err = service.Initialize(serviceURL, logger)
Expect(err).NotTo(HaveOccurred())

httpmock.RegisterResponder("POST", testWebhookURL, httpmock.NewErrorResponder(errors.New("dummy error")))
httpmock.RegisterResponder("POST", legacyWebhookURL, httpmock.NewErrorResponder(errors.New("dummy error")))

err = service.Send("Message", nil)
Expect(err).To(HaveOccurred())
Expand Down
17 changes: 17 additions & 0 deletions pkg/util/docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package util

import (
"fmt"

"github.com/containrrr/shoutrrr/internal/meta"
)

// DocsURL returns a full documentation URL for the current version of Shoutrrr with the path appended.
// If the path contains a leading slash, it is stripped.
func DocsURL(path string) string {
// strip leading slash if present
if len(path) > 0 && path[0] == '/' {
path = path[1:]
}
return fmt.Sprintf("https://containrrr.dev/shoutrrr/%s/%s", meta.DocsVersion, path)
}
4 changes: 0 additions & 4 deletions pkg/util/partition_message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,10 @@ func testPartitionMessage(hundreds int, limits types.MessageLimit, distance int)

items, omitted = PartitionMessage(builder.String(), limits, distance)

println(hundreds, len(items), omitted)

contentSize := Min(hundreds*100, limits.TotalChunkSize)
expectedChunkCount := CeilDiv(contentSize, limits.ChunkSize-1)
expectedOmitted := Max(0, (hundreds*100)-contentSize)

println(contentSize, expectedChunkCount, expectedOmitted)

Expect(omitted).To(Equal(expectedOmitted))
Expect(len(items)).To(Equal(expectedChunkCount))

Expand Down
13 changes: 13 additions & 0 deletions pkg/util/util_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package util_test

import (
"fmt"
"reflect"
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"github.com/containrrr/shoutrrr/internal/meta"
. "github.com/containrrr/shoutrrr/pkg/util"
)

Expand Down Expand Up @@ -91,4 +93,15 @@ var _ = Describe("the util package", func() {
Expect(IsNumeric(reflect.TypeOf("3").Kind())).To(BeFalse())
})
})

When("calling function DocsURL", func() {
It("should return the expected URL", func() {
expectedBase := fmt.Sprintf(`https://containrrr.dev/shoutrrr/%s/`, meta.DocsVersion)
Expect(DocsURL(``)).To(Equal(expectedBase))
Expect(DocsURL(`services/logger`)).To(Equal(expectedBase + `services/logger`))
})
It("should strip the leading slash from the path", func() {
Expect(DocsURL(`/foo`)).To(Equal(DocsURL(`foo`)))
})
})
})
6 changes: 6 additions & 0 deletions shoutrrr.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package shoutrrr

import (
"github.com/containrrr/shoutrrr/internal/meta"
"github.com/containrrr/shoutrrr/pkg/router"
"github.com/containrrr/shoutrrr/pkg/types"
)
Expand Down Expand Up @@ -32,3 +33,8 @@ func CreateSender(rawURLs ...string) (*router.ServiceRouter, error) {
func NewSender(logger types.StdLogger, serviceURLs ...string) (*router.ServiceRouter, error) {
return router.New(logger, serviceURLs...)
}

// Version returns the shoutrrr version
func Version() string {
return meta.Version
}

0 comments on commit f16b81d

Please sign in to comment.