Skip to content
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
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ toolchain go1.22.8
require (
bou.ke/monkey v1.0.2
github.com/gorilla/mux v1.8.1
github.com/levigross/exp-html v0.0.0-20120902181939-8df60c69a8f5
github.com/mattermost/mattermost/server/public v0.1.9
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.10.0
github.com/thoas/go-funk v0.9.3
go.uber.org/atomic v1.11.0
golang.org/x/oauth2 v0.21.0
)
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/levigross/exp-html v0.0.0-20120902181939-8df60c69a8f5 h1:W7p+m/AECTL3s/YR5RpQ4hz5SjNeKzZBl1q36ws12s0=
github.com/levigross/exp-html v0.0.0-20120902181939-8df60c69a8f5/go.mod h1:QMe2wuKJ0o7zIVE8AqiT8rd8epmm6WDIZ2wyuBqYPzM=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
Expand Down Expand Up @@ -183,12 +185,15 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw=
github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
Expand Down
3 changes: 3 additions & 0 deletions server/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ type Client interface {
// or a fully-qualified URL, with a non-empty scheme.
type RESTService interface {
GetSelf() (*types.ConfluenceUser, error)
GetSpaceData(string) (*SpaceResponse, error)
GetPageData(int) (*PageResponse, error)
GetSpaceKeyFromSpaceID(int64) (string, error)
}
171 changes: 171 additions & 0 deletions server/client_server.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
package main

import (
"fmt"
"net/http"
"strconv"
"strings"

"github.com/pkg/errors"

"github.com/mattermost/mattermost-plugin-confluence/server/serializer"
"github.com/mattermost/mattermost-plugin-confluence/server/service"
"github.com/mattermost/mattermost-plugin-confluence/server/util"
"github.com/mattermost/mattermost-plugin-confluence/server/util/types"
)

const (
PathCurrentUser = "/rest/api/user/current"
PathContentData = "/rest/api/content/"
PathSpaceData = "/rest/api/space/"
PathAdminData = "/rest/api/audit"
)

const (
Comment = "comment"
Space = "space"
Page = "page"
)

const pageSize = 10

type confluenceServerClient struct {
URL string
HTTPClient *http.Client
Expand All @@ -31,6 +46,66 @@ type AdminData struct {
Units string `json:"units"`
}

type SpaceResponse struct {
ID int64 `json:"id"`
Key string `json:"key"`
Name string `json:"name"`
Links Links `json:"_links"`
}

type CommentContainer struct {
ID string `json:"id"`
Type string `json:"type"`
Title string `json:"title"`
Links Links `json:"_links"`
}

type Links struct {
Self string `json:"webui"`
}

type View struct {
Value string `json:"value"`
}

type Body struct {
View View `json:"view"`
}

type CreatedBy struct {
Username string `json:"username"`
}

type History struct {
CreatedBy CreatedBy `json:"createdBy"`
}

type CommentResponse struct {
ID string `json:"id"`
Title string `json:"title"`
Space SpaceResponse `json:"space"`
Container CommentContainer `json:"container"`
Body Body `json:"body"`
Links Links `json:"_links"`
History History `json:"history"`
}

type PageResponse struct {
ID string `json:"id"`
Title string `json:"title"`
Space SpaceResponse `json:"space"`
Body Body `json:"body"`
Links Links `json:"_links"`
History History `json:"history"`
}

type ConfluenceServerEvent struct {
Comment *CommentResponse
Page *PageResponse
Space *SpaceResponse
BaseURL string
}

func newServerClient(url string, httpClient *http.Client) Client {
return &confluenceServerClient{
URL: url,
Expand All @@ -52,3 +127,99 @@ func (csc *confluenceServerClient) GetSelf() (*types.ConfluenceUser, error) {

return confluenceUser, nil
}

func (csc *confluenceServerClient) GetEventData(webhookPayload *serializer.ConfluenceServerWebhookPayload) (*ConfluenceServerEvent, error) {
var confluenceServerEvent ConfluenceServerEvent
var err error

if strings.Contains(webhookPayload.Event, Comment) {
confluenceServerEvent.Comment, err = csc.GetCommentData(webhookPayload)
if err != nil {
return nil, errors.Errorf("error getting comment data for the event. CommentID %d. Error: %v", webhookPayload.Comment.ID, err)
}
}

if strings.Contains(webhookPayload.Event, Page) {
confluenceServerEvent.Page, err = csc.GetPageData(int(webhookPayload.Page.ID))
if err != nil {
return nil, errors.Errorf("error getting page data for the event. PageID %d. Error: %v", webhookPayload.Page.ID, err)
}
}

if strings.Contains(webhookPayload.Event, Space) {
confluenceServerEvent.Space, err = csc.GetSpaceData(webhookPayload.Space.SpaceKey)
if err != nil {
return nil, errors.Errorf("error getting space data for the event. SpaceKey %s. Error: %v", webhookPayload.Space.SpaceKey, err)
}
}

return &confluenceServerEvent, nil
}

func (csc *confluenceServerClient) GetCommentData(webhookPayload *serializer.ConfluenceServerWebhookPayload) (*CommentResponse, error) {
commentResponse := &CommentResponse{}
if _, _, err := service.CallJSONWithURL(csc.URL, fmt.Sprintf("%s%s?expand=body.view,container,space,history", PathContentData, strconv.FormatInt(webhookPayload.Comment.ID, 10)), http.MethodGet, nil, commentResponse, csc.HTTPClient); err != nil {
return nil, err
}

commentResponse.Body.View.Value = util.GetBodyForExcerpt(commentResponse.Body.View.Value)

return commentResponse, nil
}

func (csc *confluenceServerClient) GetPageData(pageID int) (*PageResponse, error) {
pageResponse := &PageResponse{}
if _, _, err := service.CallJSONWithURL(csc.URL, fmt.Sprintf("%s%s?status=any&expand=body.view,container,space,history", PathContentData, strconv.Itoa(pageID)), http.MethodGet, nil, pageResponse, csc.HTTPClient); err != nil {
return nil, err
}

pageResponse.Body.View.Value = util.GetBodyForExcerpt(pageResponse.Body.View.Value)

return pageResponse, nil
}

func (csc *confluenceServerClient) GetSpaceData(spaceKey string) (*SpaceResponse, error) {
spaceResponse := &SpaceResponse{}
if _, _, err := service.CallJSONWithURL(csc.URL, fmt.Sprintf("%s%s?status=any", PathSpaceData, spaceKey), http.MethodGet, nil, spaceResponse, csc.HTTPClient); err != nil {
return nil, err
}

return spaceResponse, nil
}

type apiResponse struct {
Results []struct {
ID int64 `json:"id"`
Key string `json:"key"`
Name string `json:"name"`
} `json:"results"`
Size int `json:"size"`
}

func (csc *confluenceServerClient) GetSpaceKeyFromSpaceID(spaceID int64) (string, error) {
start := 0

for {
path := fmt.Sprintf("%s?start=%d&limit=%d", PathSpaceData, start, pageSize)

response := &apiResponse{}

if _, _, err := service.CallJSONWithURL(csc.URL, path, http.MethodGet, nil, response, csc.HTTPClient); err != nil {
return "", errors.Wrap(err, "confluence GetSpaceKeyFromSpaceID")
}

for _, space := range response.Results {
if space.ID == spaceID {
return space.Key, nil
}
}

if len(response.Results) < pageSize {
break
}

start += pageSize
}

return "", fmt.Errorf("confluence GetSpaceKeyFromSpaceID: no space key found for the space id")
}
6 changes: 1 addition & 5 deletions server/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,7 @@ func executeDisconnect(p *Plugin, commArgs *model.CommandArgs, args ...string) *

disconnected, err := p.DisconnectUser(confluenceURL, commArgs.UserId)
if errors.Cause(err) == store.ErrNotFound {
errorStr := "Your account is not connected to Confluence. Please use `/confluence connect <instance url>` to connect your account."
if confluenceURL != "" {
errorStr = fmt.Sprintf("Your Mattermost account is not linked to your Confluence account at %s. Please use `/confluence connect` to connect your account.", confluenceURL)
}
return p.responsef(commArgs, errorStr)
return p.responsef(commArgs, "Your account is not connected to Confluence. Please use `/confluence connect` to connect your account.")
}
if err != nil {
return p.responsef(commArgs, "Could not complete the **disconnection** request. Error: %v", err)
Expand Down
77 changes: 74 additions & 3 deletions server/confluence_server.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package main

import (
"encoding/json"
"io"
"net/http"
"strings"

"github.com/mattermost/mattermost-plugin-confluence/server/config"
"github.com/mattermost/mattermost-plugin-confluence/server/serializer"
"github.com/mattermost/mattermost-plugin-confluence/server/service"
"github.com/mattermost/mattermost-plugin-confluence/server/store"
)

var confluenceServerWebhook = &Endpoint{
Expand All @@ -15,17 +19,84 @@ var confluenceServerWebhook = &Endpoint{
RequiresAdmin: false,
}

func handleConfluenceServerWebhook(w http.ResponseWriter, r *http.Request, _ *Plugin) {
func handleConfluenceServerWebhook(w http.ResponseWriter, r *http.Request, p *Plugin) {
config.Mattermost.LogInfo("Received confluence server event.")

if status, err := verifyHTTPSecret(config.GetConfig().Secret, r.FormValue("secret")); err != nil {
http.Error(w, err.Error(), status)
return
}

event := serializer.ConfluenceServerEventFromJSON(r.Body)
go service.SendConfluenceNotifications(event, event.Event)
if p.serverVersionGreaterthan9 {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

var event *serializer.ConfluenceServerWebhookPayload
err = json.Unmarshal(body, &event)
if err != nil {
config.Mattermost.LogError("Error occurred while unmarshalling Confluence server webhook payload.", "Error", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

pluginConfig := config.GetConfig()
instanceID := pluginConfig.ConfluenceURL

mmUserID, err := store.GetMattermostUserIDFromConfluenceID(instanceID, event.UserKey)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
connection, err := store.LoadConnection(instanceID, *mmUserID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

client, err := p.GetServerClient(instanceID, connection)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

var spaceKey string
if strings.Contains(event.Event, Space) {
spaceKey, err = client.(*confluenceServerClient).GetSpaceKeyFromSpaceID(event.Space.ID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
event.Space.SpaceKey = spaceKey
}

eventData, err := p.GetEventData(event, client)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
eventData.BaseURL = pluginConfig.ConfluenceURL

notification := p.getNotification()

notification.SendConfluenceNotifications(eventData, event.Event, p.BotUserID, *mmUserID)
} else {
event := serializer.ConfluenceServerEventFromJSON(r.Body)
go service.SendConfluenceNotifications(event, event.Event)
}

w.Header().Set("Content-Type", "application/json")
ReturnStatusOK(w)
}

func (p *Plugin) GetEventData(webhookPayload *serializer.ConfluenceServerWebhookPayload, client Client) (*ConfluenceServerEvent, error) {
eventData, err := client.(*confluenceServerClient).GetEventData(webhookPayload)
if err != nil {
p.API.LogError("Error occurred while fetching event data.", "Error", err.Error())
return nil, err
}

return eventData, nil
}
Loading
Loading