Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(teams): use correct path in webhook URL #188

Merged
merged 4 commits into from
Aug 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}