fix: fix multiline template rendering and add test (#59)

This commit is contained in:
Robert Kaussow 2023-12-06 21:53:01 +01:00 committed by GitHub
parent af84b2a8af
commit 4221876450
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 133 additions and 21 deletions

View File

@ -36,8 +36,16 @@ properties:
Golang template for the message. The [Metadata struct](https://pkg.go.dev/github.com/thegeeklab/wp-plugin-go/plugin#Metadata)
is exposed to the template and all fields can be referenced. To extend the functionality, [sprig functions](https://masterminds.github.io/sprig/) can also be used.
defaultValue: |
Status: **{{ .Pipeline.Status }}**<br/>
Build: [{{ .Repository.Slug }}]({{ .Pipeline.URL }}){{ if .Curr.Branch }} ({{ .Curr.Branch }}){{ end }} by {{ .Curr.Author.Name }}<br/>
Status: **{{ .Pipeline.Status }}**
Build: [{{ .Repository.Slug }}]({{ .Pipeline.URL }}){{ if .Curr.Branch }} ({{ .Curr.Branch }}){{ end }} by {{ .Curr.Author.Name }}
Message: {{ .Curr.Message }}{{ if .Curr.URL }} ([source]({{ .Curr.URL }})){{ end }}
type: string
required: false
- name: template_unsafe
description: |
By default, raw HTML and potentially dangerous links in the template are not rendered. If you want to use inline HTML, you may need to turn this on.
In such cases, please ensure that the CI configuration files in the Git repository are protected against malicious changes.
defaultValue: false
type: bool
required: false

View File

@ -11,13 +11,6 @@ import (
"github.com/urfave/cli/v2"
)
//nolint:lll
const defaultTemplate = `
Status: **{{ .Pipeline.Status }}**<br/>
Build: [{{ .Repository.Slug }}]({{ .Pipeline.URL }}){{ if .Curr.Branch }} ({{ .Curr.Branch }}){{ end }} by {{ .Curr.Author.Name }}<br/>
Message: {{ .Curr.Title }}{{ if .Curr.URL }} ([source]({{ .Curr.URL }})){{ end }}
`
// settingsFlags has the cli.Flags for the plugin.Settings.
func settingsFlags(settings *plugin.Settings, category string) []cli.Flag {
return []cli.Flag{
@ -68,9 +61,16 @@ func settingsFlags(settings *plugin.Settings, category string) []cli.Flag {
Name: "template",
EnvVars: []string{"PLUGIN_TEMPLATE", "MATRIX_TEMPLATE"},
Usage: "message template",
Value: defaultTemplate,
Value: plugin.DefaultMessageTemplate,
Destination: &settings.Template,
Category: category,
},
&cli.BoolFlag{
Name: "template-unsafe",
EnvVars: []string{"PLUGIN_TEMPLATE_UNSAFE", "MATRIX_TEMPLATE_UNSAFE"},
Usage: "render raw HTML and potentially dangerous links in template",
Destination: &settings.TemplateUnsafe,
Category: category,
},
}
}

View File

@ -10,6 +10,7 @@ import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"github.com/microcosm-cc/bluemonday"
@ -75,14 +76,11 @@ func (p *Plugin) Execute() error {
return fmt.Errorf("failed to join room: %w", err)
}
message, err := template.RenderTrim(p.Network.Context, *p.Network.Client, p.Settings.Template, p.Metadata)
content, err := p.messageContent(p.Network.Context, *p.Network.Client)
if err != nil {
return fmt.Errorf("failed to render template: %w", err)
}
formatted := bluemonday.UGCPolicy().SanitizeBytes([]byte(message))
content := format.RenderMarkdown(string(formatted), true, false)
if _, err := client.SendMessageEvent(joined.RoomID, event.EventMessage, content); err != nil {
return fmt.Errorf("failed to submit message: %w", err)
}
@ -92,6 +90,26 @@ func (p *Plugin) Execute() error {
return nil
}
func (p *Plugin) messageContent(ctx context.Context, client http.Client) (event.MessageEventContent, error) {
message, err := template.RenderTrim(ctx, client, p.Settings.Template, p.Metadata)
if err != nil {
return event.MessageEventContent{}, err
}
content := format.RenderMarkdown(message, true, p.Settings.TemplateUnsafe)
safeBody := format.HTMLToMarkdown(bluemonday.UGCPolicy().Sanitize(content.FormattedBody))
if content.Body != safeBody {
content.Body = safeBody
}
if content.FormattedBody != "" {
content.FormattedBody = bluemonday.UGCPolicy().Sanitize(content.FormattedBody)
}
return content, nil
}
func prepend(prefix, input string) string {
if strings.TrimSpace(input) == "" {
return input

78
plugin/impl_test.go Normal file
View File

@ -0,0 +1,78 @@
package plugin
import (
"context"
"net/http"
"testing"
wp "github.com/thegeeklab/wp-plugin-go/plugin"
)
func Test_messageContent(t *testing.T) {
//nolint:lll
tests := []struct {
name string
want string
unsafe bool
template string
meta wp.Metadata
}{
{
name: "render default template",
want: "Status: **success**\nBuild: [octocat/demo](https://ci.example.com) (main) by octobot\nMessage: feat: demo commit title ([source](https://git.example.com))",
template: DefaultMessageTemplate,
},
{
name: "render unsafe html template",
want: "Status: **success**\nBuild: octocat/demo",
unsafe: true,
template: "Status: **{{ .Pipeline.Status }}**<br>Build: {{ .Repository.Slug }}",
},
{
name: "render html xss template",
want: "Status: **success**\nBuild: octocat/demo",
unsafe: true,
template: "Status: **{{ .Pipeline.Status }}**<br>Build: <a href=\"javascript:alert('XSS1')\" onmouseover=\"alert('XSS2')\">{{ .Repository.Slug }}<a>",
},
{
name: "render markdown xss template",
want: "Status: **success**\nBuild: octocat/demo",
unsafe: true,
template: "Status: **{{ .Pipeline.Status }}**<br>Build: [{{ .Repository.Slug }}](javascript:alert(XSS1'))",
},
}
options := wp.Options{
Name: "wp-matrix",
Execute: func(ctx context.Context) error { return nil },
}
p := New(options, &Settings{})
p.Metadata = wp.Metadata{
Curr: wp.Commit{
Branch: "main",
Title: "feat: demo commit title",
URL: "https://git.example.com",
Author: wp.Author{
Name: "octobot",
},
},
Pipeline: wp.Pipeline{
Status: "success",
URL: "https://ci.example.com",
},
Repository: wp.Repository{
Slug: "octocat/demo",
},
}
for _, tt := range tests {
p.Settings.Template = tt.template
p.Settings.TemplateUnsafe = tt.unsafe
content, _ := p.messageContent(context.Background(), http.Client{})
if content.Body != tt.want {
t.Errorf("messageContent: %q got: %q, want: %q", tt.name, content.Body, tt.want)
}
}
}

View File

@ -10,6 +10,13 @@ import (
wp "github.com/thegeeklab/wp-plugin-go/plugin"
)
//nolint:lll
const DefaultMessageTemplate = `
Status: **{{ .Pipeline.Status }}**
Build: [{{ .Repository.Slug }}]({{ .Pipeline.URL }}){{ if .Curr.Branch }} ({{ .Curr.Branch }}){{ end }} by {{ .Curr.Author.Name }}
Message: {{ .Curr.Title }}{{ if .Curr.URL }} ([source]({{ .Curr.URL }})){{ end }}
`
// Plugin implements provide the plugin.
type Plugin struct {
*wp.Plugin
@ -18,13 +25,14 @@ type Plugin struct {
// Settings for the plugin.
type Settings struct {
Username string
Password string
UserID string
AccessToken string
Homeserver string
RoomID string
Template string
Username string
Password string
UserID string
AccessToken string
Homeserver string
RoomID string
Template string
TemplateUnsafe bool
}
func New(options wp.Options, settings *Settings) *Plugin {