Skip to content

Commit

Permalink
Add notification support and verification
Browse files Browse the repository at this point in the history
Verify the signature on the notify message.
  • Loading branch information
Dschoordsch committed Nov 6, 2024
1 parent 7e359eb commit f4b71d6
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 10 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This plugin is very much work in progress and not yet ready for testing.

## Requirements

- Mattermost version 8.0 or later
- Mattermost version 8.1 or later
- `SiteURL` If the `SiteURL` is not set correctly, some functions like notifications will not work.
- SSO identity provider for both Mattermost and Parabol

Expand Down
14 changes: 12 additions & 2 deletions server/parabol_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,22 @@ func getJson(body io.ReadCloser, target interface{}) error {

func NewSigningClient(privKey []byte) (*httpsign.Client, error) {
signer, err := httpsign.NewJWSSigner(jwa.SignatureAlgorithm("HS256"), privKey, httpsign.NewSignConfig().SignAlg(false),
httpsign.Headers("@request-target", "Content-Digest"))
httpsign.Headers("@target-uri", "Content-Digest"))
if err != nil {
return nil, err
}

client := httpsign.NewDefaultClient(httpsign.NewClientConfig().SetSignatureName("sig1").SetSigner(signer))
client := httpsign.NewDefaultClient(httpsign.NewClientConfig().SetSignatureName("mattermost").SetSigner(signer))
return client, nil
}

func NewVerifier(privKey []byte) (*httpsign.Verifier, error) {
//verifier, err := httpsign.NewHMACSHA256Verifier(privKey, httpsign.NewVerifyConfig(), httpsign.Headers("@target-uri", "content-digest"))
verifier, err := httpsign.NewJWSVerifier(jwa.SignatureAlgorithm("HS256"), privKey, httpsign.NewVerifyConfig(),
httpsign.Headers("@target-uri", "content-digest"))
if err != nil {
return nil, err
}

return verifier, nil
}
39 changes: 33 additions & 6 deletions server/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin"
"github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/logger"

"github.com/yaronf/httpsign"
)

const (
Expand Down Expand Up @@ -76,21 +78,47 @@ func (p *Plugin) authenticated(handler HTTPHandlerFuncWithContext) http.HandlerF
}
}

/*
Mattermost strips the plugin path prefix from the request before forwarding it to the plugin.
If we want to verify the path of the request, we need to add it back.
https://github.com/mattermost/mattermost/blob/751d84bf13aa63f4706843318e45e8ca8401eba5/server/channels/app/plugin_requests.go#L226
*/
func (p *Plugin) fixedPath(handler http.HandlerFunc) http.HandlerFunc {
// from 10.1 we can use p.API.GetPluginID()
pluginID := "co.parabol.action"
path := "/plugins/" + pluginID
return func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = path + r.URL.Path
handler(w, r)
}
}

func (p *Plugin) notify(w http.ResponseWriter, r *http.Request) {
config := p.getConfiguration()
privKey := []byte(config.ParabolToken)
verifier, err := NewVerifier(privKey)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"error": "Verify config error"}`))
return
}
err = httpsign.VerifyRequest("parabol", *verifier, r)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"error": "Verification error"}`))
return
}

teamId := r.PathValue("teamId")
userId, err := p.API.KVGet(botUserID)
fmt.Println("GEORG teamId", teamId)

var props map[string]interface{}
err3 := getJson(r.Body, &props)
if err3 != nil {
fmt.Println("GEORG err3", err3)
return
}
channels, err2 := p.getChannels(teamId)
fmt.Println("GEORG channels", channels)
if err2 != nil {
fmt.Println("GEORG err2", err2)
return
}
for _, channel := range channels {
Expand All @@ -99,7 +127,6 @@ func (p *Plugin) notify(w http.ResponseWriter, r *http.Request) {
Props: props,
UserId: string(userId),
})
fmt.Println("GEORG post err", err)
}
}

Expand Down Expand Up @@ -204,7 +231,7 @@ func (p *Plugin) unlinkTeam(c *Context, w http.ResponseWriter, r *http.Request)

func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
mux := http.NewServeMux()
mux.HandleFunc("POST /notify/{teamId}", p.notify)
mux.HandleFunc("POST /notify/{teamId}", p.fixedPath(p.notify))
mux.HandleFunc("POST /query/{query}", p.authenticated(p.query))
mux.HandleFunc("/linkedTeams/{channelId}", p.authenticated(p.linkedTeams))
mux.HandleFunc("POST /linkTeam/{channelId}/{teamId}", p.authenticated(p.linkTeam))
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/components/select/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const LinkTeamModal = <T extends IdName>(props: SelectProps<T>) => {
value={selected && {value: selected.id, label: selected.name}}
options={options.map(({id, name}) => ({value: id, label: name}))}
onChange={(value) => onChange(options.find(({id}) => id === value?.value) ?? null)}
styles={{menuPortal: (base) => ({...base, zIndex: 9999})}}
styles={{ menuPortal: (base) => ({ ...base, zIndex: 9999 }) }}
menuPortalTarget={document.body}
isSearchable={true}
menuPosition='fixed'
Expand Down

0 comments on commit f4b71d6

Please sign in to comment.